# DeepSeek Harness TUI 用户手册 `dsh-oc-tui` 是运行在 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)进程内的**终端界面(TUI)插件**,以 profile app 的形式挂载。它把 dsh 的持久事件流渲染到终端里——流式回复、工具卡片、待办列表、思考块——并把你输入的内容送回 agent。 它**不是**独立的 agent 运行时:模型路由、工具执行、授权、命令、会话存储和凭据仍由 dsh 负责,插件只负责终端里的输入与显示。 本手册面向**安装与使用**,开发者回路见[第 8 节](#8-开发)。 ## 目录 - [1. 功能特性](#1-功能特性) - [2. 安装前准备](#2-安装前准备) - [3. 安装](#3-安装) - [4. 启动](#4-启动) - [5. 使用](#5-使用) - [5.1 快捷键](#51-快捷键) - [5.2 斜杠命令](#52-斜杠命令) - [5.3 交互式提示:授权与提问](#53-交互式提示授权与提问) - [5.4 思考强度](#54-思考强度) - [5.5 会话统计与上下文仪表](#55-会话统计与上下文仪表) - [5.6 设置菜单](#56-设置菜单) - [5.7 程序内更新](#57-程序内更新) - [6. 工作原理](#6-工作原理) - [7. 故障排查](#7-故障排查) - [8. 开发](#8-开发) - [9. 已知限制](#9-已知限制) - [10. 目录结构](#10-目录结构) - [11. 许可证](#11-许可证) ## 1. 功能特性 | | | | --- | --- | | **持久会话** | 新建、恢复、列出、删除会话;转录内容从持久事件日志重建,恢复后的会话与离开时完全一致。 | | **实时流式** | 回复与思考逐 token 流式渲染;思考内容在独立可折叠框中,流式期间保持折叠。 | | **工具活动** | 工具卡片带一行摘要(`read src/app.ts`、`run npm test`),运行中显示流动 spinner,结果按 Markdown 渲染。 | | **交互式提问** | 模型可以暂停并向你提问——选项列表、多选、自由文本、可滚动的计划评审,全部在终端内完成。 | | **内联授权** | `approval/request` 询问用 `y` / `n` 直接回答,无需离开界面。 | | **遥测页脚** | 会话统计条:轮次/步数、LLM 与工具耗时、平均首 token 时间(TTFT)、解码吞吐、KV 缓存命中率与输入/输出 token,均由持久事件折叠得出。 | | **统计窗口** | 点击统计条或上下文仪表条,或输入 `/stats`,打开会话统计与 token 用量明细窗口。 | | **上下文仪表** | 实时上下文占用(`ctx ▓▓░░ 32K/128K 25%`),明细窗口内含按系统提示词/工具/消息拆分的构成占比。 | | **思考强度** | `Tab` 循环切换当前模型真实支持的推理等级;`Ctrl+E` 打开滑块。选择按请求应用并持久化。 | | **共享设置** | 与 WebUI 相同的 Host 设置命名空间——通用、会话、各 provider 模型配置、凭据——持久化到 `$DSH_HOME/settings.yaml`。 | | **程序内更新** | 在 TUI 内检测并切换 `@deepseek-ai/dsh` 与 `dsh-oc-tui` 版本,Windows 上采用延迟安装避免静默损坏。 | | **零依赖终端引擎** | raw 模式、备用屏幕、差分单元缓冲、真彩 ANSI、CJK 宽度感知、SGR + 旧式 X10 鼠标解码、IME 光标锚定。 | ## 2. 安装前准备 | 项目 | 要求 | | --- | --- | | Node.js | >= 22 | | dsh CLI | 已安装 `@deepseek-ai/dsh`,例如 `npm install -g @deepseek-ai/dsh` | | pnpm | 已加入 PATH(`dsh plugin` 内部转发给 pnpm) | | 终端 | 交互式终端(Windows Terminal / ConPTY、iTerm2、GNOME Terminal 等) | | 模型配置 | `$DSH_HOME/settings.yaml` 与 `$DSH_HOME/.credentials.yaml` 中已配置可用的模型路由(与 Web GUI 共用) | ```sh dsh --version pnpm --version ``` **兼容性**:本插件已在 dsh `0.1.2-rc.1`(以及 `0.1.1-rc.2`)上验证。dsh 0.1.2 重命名了部分会话 API——`Session.events` 改为 `snapshotEvents()`——插件会读取宿主实际提供的那个访问器,因此同一份构建可同时服务这两条版本线。 ## 3. 安装 ### 3.1 从 npm 安装 本包已发布到 npm,包名 [`dsh-oc-tui`](https://www.npmjs.com/package/dsh-oc-tui)。装进 `tui` profile: ```sh dsh plugin --profile tui add -w dsh-oc-tui ``` 也可以把启动器装到全局,这样 `dsh-oc-tui` 命令就在 PATH 上,它等价于 `dsh --profile tui`: ```sh npm install -g dsh-oc-tui ``` 本插件同时已收录进 [awesome-dsh-plugin](https://awesome-dsh-plugin.com/zh/p/rayafriandion/dsh-oc-tui/) 插件市场(分类:UI 增强)。 ### 3.2 版本渠道:只有稳定版 **npm 包与 awesome-dsh-plugin 市场收录都只提供稳定版(stable release),不含任何预发布(pre-release / rc / alpha / beta)。** 因此 `npm install` 拿到的一定是最近一个稳定版,而不会是候选版本。 本手册描述的是当前源码树,可能领先于已发布版本——手册里写到的功能,只有在对应版本发布到 npm 之后才能从稳定版拿到。 想用预发布版本、或本仓库里尚未发布的改动,需要显式从源码安装: ```sh npm pack # 生成 dsh-oc-tui-<版本>.tgz dsh plugin --profile tui add -w ./dsh-oc-tui-<版本>.tgz ``` ### 3.3 一键安装脚本 仓库内置 Linux/macOS 与 Windows 安装脚本:检查 Node.js >= 22、确保 pnpm 可用、把插件装进 `tui` profile,加 `--launcher`/`-Launcher` 还会全局安装 `dsh-oc-tui` 启动器。 ```sh # Linux / macOS curl -fsSL https://raw.githubusercontent.com/rayafriandion/dsh-oc-tui/main/install.sh | bash ``` ```powershell # Windows(PowerShell) powershell -ExecutionPolicy Bypass -Command "iwr https://raw.githubusercontent.com/rayafriandion/dsh-oc-tui/main/install.ps1 -OutFile install.ps1; & .\install.ps1" ``` 在源码目录里也可直接运行 `./install.sh`(Linux/macOS)或 `.\install.ps1`(Windows)。其他选项:`--local`(`-Local`)安装当前源码目录;`--source `(`-Source `)指定自定义来源;`--profile `(`-Profile `)指定非默认 profile。 ### 3.4 从本地目录或 tarball 安装 ```sh npm pack # 生成 dsh-oc-tui-<版本>.tgz dsh plugin --profile tui add -w ./dsh-oc-tui-<版本>.tgz ``` `dsh plugin` 会把相对路径按你执行命令时所在的目录解析后再转发给 pnpm。 ### 3.5 为什么必须带 `-w` profile 目录把自己声明为 pnpm workspace 根(`pnpm-workspace.yaml` → `packages: [.]`),因此裸 `add` 会被 pnpm 拒绝并报 `ERR_PNPM_ADDING_TO_ROOT`。加上 `-w` 后依赖写入 profile 自己的 manifest——这正是它应有的位置。之后 `dsh plugin` 会把 `dsh.profile.bundles` 与已安装状态对齐。 ### 3.6 安装做了什么 1. `dsh plugin` 首次使用时初始化 `$DSH_HOME/profiles/tui`(`@deepseek-ai/dsh-base` 加一个空的用户补丁层)。 2. pnpm 把 `dsh-oc-tui` 装进该 profile 的 `node_modules`。 3. 因为本包声明了 `dsh.bundle.patch`,dsh 自动把 `dsh-oc-tui` 追加到 `dsh.profile.bundles`。 4. `dsh --profile tui` 组合 base 层、本 bundle 的行、以及你自己的补丁——无需手动编辑任何配置。 ### 3.7 验证安装 ```sh dsh --profile tui --dump-config ``` 输出中应出现 `# == dsh-oc-tui` 这一层,包含 `tui-startup`、`tui-app`、`agent-presets`(预设名册)与 `tool-ask-user`(提问工具)四行。 ## 4. 启动 ### 4.1 标准启动 ```sh dsh --profile tui ``` 启动后先进入标题界面,**发送第一条消息即创建会话**。常用参数: ```sh dsh --profile tui --resume # 恢复已保存的会话 dsh --profile tui --model # 新会话默认模型 dsh --profile tui --provider # 新会话默认 provider 路由 dsh --profile tui --no-sidebar # 不显示侧边栏 dsh --profile tui --help # 查看 TUI 自己的参数帮助 ``` dsh 启动器只把 `web` 和 `plugin` 硬编码为裸子命令,所以 `--profile tui` 才是正规形态。想要直接敲 `dsh tui`,可以加一行别名: ```powershell function tui { dsh --profile tui @args } # 写入 PowerShell $PROFILE ``` ```bat doskey tui=dsh --profile tui $* :: CMD ``` ### 4.2 便捷启动器 dsh-oc-tui 本包附带 `dsh-oc-tui` 命令,等价于 `dsh --profile tui`,但会在启动前检查 `tui` profile 是否已装好本插件;若没有,会直接打印一次性安装命令,而不是进入一个空转的 profile。 ```sh dsh-oc-tui # 启动 tui profile dsh-oc-tui --profile mytui # 启动名为 mytui 的 profile dsh-oc-tui --help # 启动器帮助 dsh-oc-tui --version # 启动器版本 ``` 它优先使用 PATH 中的 `dsh`,找不到时回退到 `npx --yes @deepseek-ai/dsh`。安装到 PATH:`npm install -g dsh-oc-tui`。 | 环境变量 | 作用 | | --- | --- | | `DSH_TUI_PROFILE` | 未指定 `--profile` 时的默认 profile 名(默认 `tui`) | | `DSH_TUI_SKIP_CHECK` | 设为 `1` 跳过 profile 预检(高级安装方式使用) | ## 5. 使用 ### 5.1 快捷键 | 按键 | 作用 | | --- | --- | | `Enter` | 发送消息 | | `Ctrl+Enter` / `Shift+Enter` / `Alt+Enter` | 输入换行 | | `Ctrl+C` | 清空非空输入框 / 取消进行中的轮次;空闲时连按两次退出 | | `Ctrl+P` | 打开设置菜单 | | `Ctrl+E` | 打开/关闭输入框下方的思考强度滑块 | | `Tab` | 会话页面:循环切换思考强度;设置页面:切换左侧菜单(Main/Model/Update) | | `Ctrl+N` | 新建会话 | | `Ctrl+D` | 在 设置 → 管理会话 中删除当前选中的会话(连按两次确认) | | `Ctrl+L` | 清空当前转录视图 | | `Up` / `Down` | 在多行输入中上下移动光标;位于第一行/最后一行时改为浏览输入历史 | | `Left` / `Right` | 在输入框中左右移动光标 | | `PgUp` / `PgDn` | 滚动转录区 | | `Esc` | 关闭会话统计窗口 / 关闭强度滑块 / 关闭帮助 / 取消授权询问 / 中断正在运行的轮次 / 清空正在输入的提示词 | | `Esc Esc` | 空闲且输入框为空时打开 rewind 选择器 | | `y` / `n` | 回答界面内的授权询问 | **鼠标**:滚轮滚动页面——会话页滚动转录区,设置窗口打开时滚动设置窗口。在转录区按住左键拖动可选中文字,随后按右键把所选文字复制到剪贴板。会话页的统计条与上下文仪表条都是点击目标(见 [5.5](#55-会话统计与上下文仪表))。 ### 5.2 斜杠命令 内建命令:`/help` `/settings` `/new` `/resume ` `/model ` `/provider ` `/rewind` `/stats` `/clear` `/cancel` `/quit`(`/exit` 等效)。 **Rewind(回退)**:`Esc Esc`(或 `/rewind`)列出当前会话的提示词。选择"恢复对话"会以所选提示词之前的事件**分叉出一个新会话**,父会话完整留在磁盘上——与 dsh 自身的 `session/fork` 行为一致;选择器默认停在最近一条真实提示词上,因此连按两次 `Enter` 即回退最后一轮。`/rewind [conversation|code|both]` 可跳过选择器直接执行。分叉出的新会话从**空 inbox** 开始:切点落在某轮 turn 之前,也会切掉那一轮对 inbox 的认领,因此父会话里排队过的输入(**包括你这次要丢掉的那条提示词**)不会被再次投递,它们仍留在父会话日志里;若有此类输入,结果行会显示 `dropped N inherited pending input`。**恢复文件**是尽力而为且有护栏的:必须在 git 工作区中(否则提示 `files not restored (not a git worktree)` 且不改动任何文件);已跟踪文件用 `HEAD` 覆盖且不触碰索引;只有当日志中该路径的首次写入发生在回退点之后时,才会删除未跟踪文件。所有被覆盖或删除的内容都会先复制到 `$DSH_HOME/rewind-backups//<时间戳>/`,结果行会给出该目录。由于日志不保存文件内容,已跟踪文件回到的是最近一次提交,而非回退点当时的状态。 dsh 的人类命令(`/compact`、`/goal`、`/plan` 等)会转发给 `ctx.commands`,不经过模型轮次执行。**这些命令需要有活动会话**:在标题屏上敲会得到 `/: start a session first` 提示,而不是被静默丢弃。因此想用 `/plan` 进入计划模式,请先随便发一条消息建立会话。 ### 5.3 交互式提示:授权与提问 **授权询问**:工具需要许可时,输入框区域显示 `Approval · <工具> · y allow / n deny`。`y` 允许一次、`n` 拒绝、`Esc` 取消。插件同时尊重生效的权限预设——自动批准的预设不会弹询问。 **模型提问**:模型可以通过 `ask_user_question` 工具直接向你提问。该工具由本 bundle 的 `tool-ask-user` 行声明(`dsh-base` 只挂载 `user-questions` **服务**、不挂载工具,而 TUI 会话是从 base 进程级组合的、不走 agent 预设),并由一个弹窗应答: | 按键 | 作用 | | --- | --- | | `Up` / `Down` | 在选项间移动(循环) | | `Space` | 切换高亮项(多选)或选中它(单选) | | `Enter` | 继续:单选会选中并前进;在自由文本行上进入编辑;多选则确认已勾选集合 | | 任意可打印字符 | 跳到自由文本行并开始输入 | | `PgUp` / `PgDn`、滚轮 | 滚动较长的计划/明细区 | | `Esc` | 让出(defer):不在本界面回答;编辑状态下按 `Esc` 先退回选项 | | `Ctrl+C` | 仍然取消进行中的轮次,待答问题随之撤回 | 问题**一次问一个**(与 WebUI 的 composer 完全一致),答案编码也相同:自由文本答案在单选问题中**替换**已选选项,在多选题中**与已选项并存**。 带 `plan-review` 意图的问题(`exit_plan_mode` 发出的那种)会把计划 markdown 渲染在上方可滚动区域,下面是 `Approve` / `Keep planning`。选 `Approve` 会退出计划模式并继续执行;选其它则继续保持计划。 `Esc` 让出是刻意设计而非"取消":若没有其它应答者,工具会收到 `no user-questions answerer accepted the request` 报错——这个结果不会被误当成你做出了选择。 ### 5.4 思考强度 输入框右上角显示当前生效的思考强度等级名,与 `provider · model` 标签对角。 - 会话页面按 `Tab` 循环当前模型支持的等级(到最高档后回到最低档),`Shift+Tab` 反向;改动即时保存。 - `Ctrl+E` 打开输入框下方的滑块:`Tab` 或 `←`/`→` 调整并保存,`Esc` 或 `Ctrl+E` 关闭。 - 等级来自 provider 适配器实际声明的可选值(`ctx.llm.resolveModelInfo`):布尔型思考模型只显示两端,DeepSeek 的 `Off`/`High`/`Max` 只显示这三档,全档位模型显示它声明的每一级——不会退化成 `none → max` 的一刀切刻度。 选择通过 `agent/request` waterfall 应用到该会话的请求,并保存到 `agent-default-model.reasoningEffort`。 ### 5.5 会话统计与上下文仪表 输入框上方一行是**会话统计条**,对应 Web 界面输入框下方的统计行——用 `│` 分隔、按同样的顺序列出同一批数字: ``` ▤ 1 turn · 2 steps│LLM 1.3s · tools 1.2s│TTFT avg 400ms · 20.0 tok/s│cache 55%│in 110 · out 30 ``` - `turns` / `steps` 统计**已结束的步**(`step/end`):失败、取消、触顶的步同样计入;`LLM` 是 `step/start` → 组装完成的回复,`tools` 是配对的 `tool/call` → `tool/result`。 - `TTFT avg` 是每步首 token 延迟的平均值,`tok/s` 是解码吞吐(首 token → 组装完成之间的输出 token)。 - `cache` 是提示词侧缓存命中率(缓存读取 ÷ 全部计费输入),`in` / `out` 是本次会话累计的计费输入与输出 token。 - 极窄终端会**整组**丢弃放不下的尾部数字并标出 `│…`,不会把某个数字截成两半;完整数字始终在明细窗口里。 - 会话还没有任何已结束的步、也没有任何 token 计费时,统计条整行隐藏,把该行还给转录区。 统计条整行是点击目标:点击它(或点击状态栏右端的上下文仪表条 `ctx ▓▓░░ 32K/128K 25%`,或输入 `/stats`)打开**会话统计窗口**,再次点击、点击别处或按 `Esc` 关闭。窗口把同一个统计条拆成明细行(`usage` / `duration` / `speed` / `tokens` / `cache`),并附上上下文占用读数与按启发式拆分的构成占比——系统提示词、工具、对话消息——对应 WebUI 的 ContextMeter 弹窗。 数据来源与 Web 界面一致,优先级为**投影优先、本地折叠兜底**:token-meter 的 `tokenUsage`、`contextPressure`、`contextBreakdown` 投影由 `dsh-base` 挂载;`sessionStats` 投影只有 Web 应用层 bundle 才挂载,因此 TUI 自己按同样的规则折叠持久日志里的 `step` / `chunk` / `message` / `tool` 事件。profile 缺少某个投影时,对应数字自动退回本地折叠而不影响其它功能;两者都不存在时(例如尚无任何事件)该行/该组自动隐藏。 ### 5.6 设置菜单 按 `Ctrl+P` 打开。菜单覆盖与 WebUI 相同的 Host 设置命名空间,修改通过 `ctx.settings` 持久化到 `$DSH_HOME/settings.yaml`。左侧菜单栏用 `Tab`(或鼠标点击)在三个标签间切换,`↑/↓` 移动、`Enter` 打开选项列表、`Esc` 返回,分区标题行不可选中: - **Main**:General(Busy Enter 行为、默认 agent 预设、权限预设)、Sessions(新建会话、管理会话)、System(Provider API 配置提示、更新管理器快捷入口、设置文件路径) - **Model**:默认 provider / model / 思考强度;随后是你添加过的每个 provider 一组,组内为 Provider URL、Provider API key、Models - **Update**:见 [5.7](#57-程序内更新) 只显示你在设置文件中**手动添加过**的 provider;系统预设但从未添加的不会显示。 **Models 自动获取**:在某个 provider 的 **Models** 行按 `Enter`,TUI 调用 `ctx.llm.discoverModels` 获取该 provider 的完整模型目录(内置路由直接取内置目录,自定义路由则请求其端点),弹出勾选窗口用 `[x]`/`[ ]` 选择(`Enter` 切换、`Esc` 返回)。选中的模型保存到该 provider 的 `models` 配置;在列出的模型上按 `Enter` 可把它设为默认模型。 默认 agent 预设来自 profile 挂载的 `agent-presets` 名册(内置预设 + 你在 `$DSH_HOME/.agent-presets` 下自建的预设)。注意 TUI 会话始终从进程级 base 组合生成,因此该默认值只在按预设创建会话时生效。 WebUI 专属选项(`ui-theme` 外观、`locale` 语言)不在 TUI 中显示,因为它们在终端里没有效果。 **第三方插件的设置页分区(目前尚不支持)**:`@dsh-std` 生态里有 `ui.dsh/v1alpha1` 的 `ContributionHost` / `UiContribution` 协议,本意是让第三方插件往宿主的设置面追加只读分区(`host-rendered` 模式,即宿主自己渲染对方给的数据)。TUI **目前不托管**这类贡献,所以设置页只显示上面列出的内置分区:本插件的 `@dsh-std` facet 无法注册贡献宿主——adapter 对 facet 提交的实现有强制校验(必须有 `handle` 函数,字段名必须是 `protocol`),而协议里的 `UiContributionProvider` 两条都不满足,提交它会让 adapter 在挂载时回滚整个 profile;真正的注册入口是 adapter 的实例方法 `registerUiContributionProvider`,facet 拿不到 adapter 实例。需要宿主自己执行第三方 JS 的 `local-module` 模式则永久不在计划内(TUI 无沙箱,且协议说明没有同一 page realm 的 TUI 不需要实现它)。另需注意:本插件的 facet 在当前上游下**一条协议 support 都不暂存**——Community v0.15 清单无法声明 supports,而 lifecycle 要求先声明才能暂存——所以它不只是不托管贡献宿主,Presentation 与 CommandRuntime 同样处于休眠状态。详见 [dsh-std 接入说明](dsh-std-接入说明.md) 开头的「阻断性发现」。 ### 5.7 程序内更新 `Ctrl+P → Update` 页面负责检测并切换 dsh 与 TUI 自身的版本,所有检查与安装都通过 `npm` / `dsh plugin`(即 pnpm)执行,因此会尊重你配置的 registry 与镜像。状态行**只以稳定版为目标**: - `Update available → x.y.z`:有更新稳定版(金色高亮) - `Up to date`:已是最新 - `No stable release — pick from Versions`:registry 上还没有稳定版,不会主动提示,需自行从版本列表选择 - `Install damaged — reinstall below`:全局 dsh 安装处于新旧混杂的损坏状态,需重装 `Enter` 打开某个包的 **Versions** 完整版本列表(新→旧),`[latest]`(绿)、`[next]`(青)、`(installed)`(浅蓝)等标识区分频道与当前版本;选中任意版本(含 rc/alpha)后按 `Enter` 出现 y/n 确认(文案含目标版本号)。`Check now` 重新拉取 registry;`Startup check` 开关启动后 2 秒的静默稳定版检查。 安装过程后台进行、不阻塞界面、不会自动重启;完成后 toast `... installed — restart to apply`,重启后生效。 **Windows 上的 dsh 更新(重要)**:Windows 会锁定运行中程序加载的原生 DLL,而 `npm install -g` 需要替换整个 dsh 目录——若更新时有任何 dsh 进程在运行,npm 可能**静默留下新旧混杂的损坏安装**并仍报成功。因此更新器有三层保护: 1. **dsh 安装在 Windows 上延迟到 TUI 退出后执行**:确认后 toast 提示 `will install when this TUI exits`,关闭 TUI 时一个独立小进程等待本进程退出、执行 `npm install`,并把结果写入 `$DSH_HOME/tui-dsh-install.json`,下次打开 Update 页自动核对。 2. **每次直接安装后校验磁盘实际版本**与目标是否一致,不一致(静默损坏)时 toast 报 `install corrupt` 并给出修复指引。 3. **已损坏的安装会被标出**(Status 行 `Install damaged — reinstall below`),而不是报一个虚假的成功。 macOS/Linux 没有 DLL 锁,但检测到其它 dsh 进程运行时也会拒绝安装。 ## 6. 工作原理 - 插件是用 `tui` profile 加载的 Cordis 函数插件。`lib/startup.js` 解析本应用的命令行参数并提供 `tuiStartup` 服务;`lib/index.js` 拥有 UI 主循环。 - `lib/term.js` 是零依赖终端引擎:raw 模式、备用屏幕、差分单元缓冲、按键解码(真彩 ANSI、CJK 宽度感知)。它把隐藏的终端光标停在输入光标处,使系统 IME 的候选窗锚定在输入框内;同时理解 SGR 与旧式 X10 两种鼠标编码,滚轮/点击字节不会漏进输入文本。 - `lib/ui.js` 是响应式视图模型与渲染器(DeepSeek 蓝白主题、会话栏、转录区、多行输入框、命令建议、会话统计条、状态栏)。转录行按块缓存,每帧只实体化可见窗口,流式绘制合并,活动块按短节流重绘——因此渲染成本有界,输出速度不随历史增长而下降。思考内容折叠以保持转录可读,运行中的工具与思考块用流动 spinner 提示。 - `lib/metrics.js` 把持久的 step/chunk/message/tool 事件折叠为整场会话的轮次/步数、耗时、TTFT、吞吐、缓存命中与计费 token(与 Web 界面的 `sessionStats` / `tokenUsage` 投影同规则),并在 profile 提供投影时以投影值为准。 - `lib/interrupt.js` 管理 stdin 与 `SIGINT` 共用的清空/取消/二次退出状态机。 - `lib/markdown.js` 把模型输出(标题、列表、引用、代码、行内样式)渲染为带样式的行。 - `lib/updates.js` 隔离 Update 页面的全部 npm/pnpm 交互——registry 查询、无依赖 semver 比较、dsh 安装探测、异步安装——一律走 `child_process.spawn`,从不使用 `spawnSync`。 - 会话通过 `ctx.agents` 创建/恢复,转录由持久日志重建并由 `session/event` 实时驱动(含 `assistant/chunk`),模型默认值来自 `ctx.agentDefaultModel`,授权询问在内联应答 `approval/request` waterfall。 - `ask_user_question` 通过 `user-questions/request` waterfall 应答:这是一个按 agent 作用域分发的 Cordis waterfall,弹窗要么返回答案、要么用 `next()` 委托。请求中止时以普通错误拒绝,让服务自己报出 `ASK_ABORTED`;发给其它 agent 的请求原样委托。 ## 7. 故障排查 | 现象 | 原因与处理 | | --- | --- | | `pnpm failed in profile directory` / `ERR_PNPM_ADDING_TO_ROOT` | profile 是 pnpm workspace 根,`add`/`remove` 要加 `-w`。 | | 安装时报 `ENOENT: … dsh-oc-tui-<版本>.tgz` | profile 仍引用你已删除的 tarball。先 `dsh plugin --profile tui remove -w dsh-oc-tui`,再重新 add。 | | `pnpm not found on PATH` | 安装 pnpm(`npm install -g pnpm`)后重试。 | | `--dump-config` 里没有 TUI 层 | 安装未完成或包名拼写不一致;重跑 add 并检查 `dsh.profile.bundles`。 | | 启动后立刻退出或没有界面 | stdin/stdout 必须都是 TTY,不要重定向或管道;再确认模型路由与凭据文件就绪。 | | `--resume` 或 管理会话 不可用 | 两者都需要共享的 `sessionQuery` 服务;确保 `dsh.profile.bundles` 第一项仍是 `@deepseek-ai/dsh-base`。 | | `--resume` 报 `no agent factory registered` | 与 agent-loop 行的启动竞态;当前构建会重试。旧构建可先启动、再用 `/resume `。 | | 更新 dsh 后启动报 loader 错误(`State`、`./internal`) | 全局 dsh 处于新旧混杂状态:关闭所有 dsh 进程后 `npm install -g @deepseek-ai/dsh@<版本>`。 | | 改了源码不生效 | profile 里装的是 tarball 副本,需要重新打包再安装(见第 8 节)。 | ## 8. 开发 ```sh npm run check # 对 lib/、bin/ 做 node --check npm test # 独立冒烟测试(不需要 dsh) ``` **安装即构建,所以顺序是:改源码 → 打包 → 重新安装。** profile 里装的是本插件的 **tarball** 副本,而 profile 的热更新监视的是 profile 目录、不是插件目录——改这个检出不会影响任何东西,除非重新打包并重装: ```sh dsh plugin --profile tui remove -w dsh-oc-tui # 先摘掉旧依赖 Remove-Item .\*.tgz # 再删掉旧 tarball npm pack dsh plugin --profile tui add -w .\dsh-oc-tui-<版本>.tgz ``` **顺序不能颠倒**:pnpm 在 add 时会解析 profile 现有的 `file:` 依赖,指向已删除 tarball 会让整个安装以 `ENOENT` 中止。 装完要**逐文件哈希核对**(版本号本身证明不了任何事): ```powershell foreach ($rel in @('lib\index.js','lib\ui.js','lib\util.js','lib\term.js','lib\metrics.js', 'lib\interrupt.js','lib\web-settings.js','lib\updates.js','lib\markdown.js', 'lib\startup.js','bin\dsh-oc-tui.js','cordis.patch.yml')) { $a = (Get-FileHash ".\$rel").Hash $b = (Get-FileHash "$env:USERPROFILE\.dsh\profiles\tui\node_modules\dsh-oc-tui\$rel").Hash if ($a -ne $b) { "DIFFERS: $rel" } } ``` 然后必须**真正跑起来**:只到标题屏不算验证——会话开启路径才是宿主 API 断裂暴露的地方,所以要发一条消息。`--resume` 要**单独测**,它是 apply 期路径,可能输掉一个其它路径不会遇到的启动竞态。 ### 8.1 零安装开发引导 想跳过打包,可以让 profile 直接引用本检出的绝对路径: ```sh dsh --profile tui --dump-config # 先初始化 base profile ``` ```yaml # $DSH_HOME/profiles/tui/cordis.patch.yml - insert: - id: tui-startup name: 'file:///path/to/dsh-oc-tui/lib/startup.js' - id: tui-app name: 'file:///path/to/dsh-oc-tui/lib/index.js' config: sidebar: true showReasoning: true ``` 插件对 dsh 各包的 import 会通过 `$DSH_HOME/profiles/node_modules` 共享回退解析,因此无需在插件目录里安装依赖。 ## 9. 已知限制 - 零依赖终端引擎尚未暴露 IME 组字;图片附件已支持:bracketed paste 的原始图片字节、`data:image/...;base64,...` 数据 URL、本地图片路径或图片 URL 都会变成 `[Image N]` 附件,而粘贴一段无法识别的文本时会向终端请求剪贴板(OSC 52)。 - 插件不支持热重载:profile 的 HMR 根是 profile 目录,运行中的 TUI 保持它启动时的那份副本。 - `dsh tui` 作为裸子命令需要 shell 别名——原版启动器只硬编码了 `web` 与 `plugin`。 - dsh 的人类斜杠命令需要活动会话;标题屏上会提示先建立会话。 - `Esc` 让出问题不会取消工具调用,而是委托;没有其它应答者时该工具调用会失败。WebUI composer 支持的**逐题跳过**尚未实现。 - `--resume`、设置 → 管理会话、会话统计条与上下文仪表依赖 `@deepseek-ai/dsh-base` 挂载的服务(`sessionQuery`、`sessionProjections`);手工搭建的 profile 需自行提供。`sessionStats` 投影只由 Web 应用层 bundle 挂载,缺少时 TUI 自行从会话日志折叠同样的数字。 - Windows 上的延迟 dsh 安装只等待**调度它的那个 TUI**,不是机器上所有 dsh 进程;执行前请关掉其它 TUI 窗口(以及 `dsh web`)。 ## 10. 目录结构 ``` lib/index.js 插件入口:agents、事件、输入、命令、授权、用户提问 lib/startup.js 命令行参数提供者(tuiStartup 服务) lib/term.js 终端引擎(raw 模式、屏幕、按键解码) lib/ui.js 响应式视图模型 + 渲染器(含问答弹窗) lib/metrics.js 整场会话统计 + token 用量折叠(Web 统计条 / tokenUsage 投影同规则) lib/interrupt.js Ctrl+C 生命周期状态机 lib/web-settings.js WebUI 设置投影 lib/updates.js 程序内更新(npm registry + 安装) lib/markdown.js Markdown -> 带样式文本行 lib/util.js 文本/显示工具 lib/bridge.js 活体 TUI 注册表(@dsh-std facet 的转发目标) lib/facet.js @dsh-std facet 入口(互操作外壳,不启动 TUI) lib/std/ @dsh-std 协议适配(adapt、presentation、commands、command-list) bin/dsh-oc-tui.js 便捷启动器 install.sh 一键安装脚本(Linux/macOS) install.ps1 一键安装脚本(Windows) dsh-plugin.json @dsh-std 组件清单(仅用于发现与预检) cordis.patch.yml bundle 补丁层(TUI 行、预设名册、提问工具) docs/用户手册.md 本手册 docs/dsh-std-接入说明.md @dsh-std 接入范围与约束 tests/smoke.test.mjs 独立冒烟测试 ``` ## 11. 许可证 [LGPL-3.0-or-later](../LICENSE)