# dsh-preset-chat

[English](README.md) | 中文

为 [DeepSeek Harness](https://deepseek.com) 增加 **聊天模式** 的 Agent preset bundle——与内置 `standard` / `ptc` / `minimal` / `cordis` 平级的纯对话模式。
新版 DSH 0.2.1 的图片兼容：生成结果通过独立的 Conversation 节点显示在最终回答后；图片组件与 imagegen 工具卡片均支持右键操作和会话内方向键切图。保留原生回答与工具渲染，不修改会话消息或项目文件。


## Preset 提供的内容

在 `cordis.patch.yml` 中以一行 `@deepseek-ai/dsh-agent-preset` 声明（`config.id: chat`，行 id `preset-chat`）：

| 插件 | 效果 |
|---|---|
| `@deepseek-ai/dsh-persona` | 完整英文人设——“各领域顶尖、无所不能、为主人服务”；回答清晰自然、**默认简体中文**、Markdown 排版，带主动搜索/澄清/生图习惯（`complete: true`，不注入运行时上下文块） |
| `@deepseek-ai/dsh-tool-ask-user` | `ask_user_question`——意图含糊时先澄清再作答 |
| `dsh-preset-chat/attachments` | `read_uploaded_file`——读取当前会话上传的文本、PDF、DOCX 和 XLSX 文档 |
| `@deepseek-ai/dsh-tool-web` | `web_search` + `web_fetch`（开启抓取，搜索超时 60s） |
| `@deepseek-ai/dsh-compaction-basic` + `command-compact` | 自动上下文压缩、工具结果裁剪、`/compact` 命令 |

profile 层能力自动可用（不归 preset 管辖）：`imagegen`（来自 [dsh-codex](https://github.com/Yan-Zero/dsh-codex)）与 `schedule_*` 提醒工具组。上传文档可通过只读工具读取；不添加 bash、任意本地文件访问、终端、子代理、goal 或 plan-mode。

聊天模式固定为项目文件只读，并禁止沙箱提权，不继承其他模式或全局默认的写权限。`/permission` 可查看这一固定策略，不能将聊天模式改为可写。切出聊天模式时恢复该会话进入聊天模式前的权限；其他会话、其他模式和全局默认设置保持不变。权限及恢复记录可跨重启保留。

聊天模式不向模型自动注入当前工作目录，发送请求前的上下文刷新也不会重新带入目录。后台目录管理和文件权限仍正常工作，其他模式继续保留原生工作目录上下文。已有会话历史中的路径及用户主动提供的路径不会被删除。

聊天模式中的 `imagegen` 沿用现有生图服务、模型设置和参考图片处理，结果保留为聊天附件，不自动写入项目文件夹，也不提供 `output_path` 参数。附件、会话、人设等应用数据仍可正常保存。其他模式的生图自动保存行为保持不变。

在聊天图片的放大预览中，按键盘 ← / → 可按出现顺序切换本会话已发送和生成的图片，包括尚未滚动加载的历史图片。同一附件只出现一次，预览显示当前位置，到首尾停止切换。Esc 先关闭右键菜单，再关闭预览。

生成的图片会持续显示在本轮聊天回答下方，不随工具过程折叠；重新打开会话或重启后也能恢复。展示直接复用现有附件，不增加消息、不额外写入项目文件。

在聊天图片缩略图或放大预览上右键，可选择「在新标签页中打开图像」「添加到聊天」「复制图像」「下载副本」「在 Finder 中显示」。新标签页使用应用内文件预览；添加到聊天只加入输入框，等待手动发送；下载副本保存原图；Finder 直接定位现有附件文件，不另外复制，也不放开模型的项目写权限。此前生成的聊天图片也支持。

## 安装

插件管理器 → 安装 bundle → `dsh-preset-chat`（npm），或指定本地 tarball：

```bash
pnpm pack   # 产出 dsh-preset-chat-<version>.tgz
```

然后通过插件管理器安装。live profile 即时生效；内容变更需要重启 Host。

## 上传文件与文档问答

点击 **＋ → 文件**、将文件拖入输入框，或粘贴图片，再带上问题一起发送。上传入口、附件卡片和文件存储均沿用 Harness 原有机制。图片走当前模型的原生视觉通道，因此需要选择支持图片的模型。

`read_uploaded_file` 可列出并读取当前会话实际发送过的文档，上下文压缩后也可读取。桌面端选择的文件以 `@路径` 引用发送，也支持读取。可按文件名、附件 ID 或用户明确选中的路径选择；不能读取未引用的其他本地文件。支持 UTF-8/UTF-16 的 TXT、Markdown、CSV/TSV、JSON/JSONL、YAML、XML、HTML 和日志；带文字层的 PDF；Word **DOCX**；Excel **XLSX**（工作表名称、单元格地址及显示值或缓存值，不执行公式）。旧版 DOC/XLS 请先转换。附件内容作为参考资料，不作为修改系统或用户指令的授权。

解析上限：单文件 20 MiB、PDF 300 页、提取文本 200 万字符、表格 10 万个非空单元格及 1000 列。每次返回最多 16000 字符，并用 `next_offset` 继续读取。解析在独立且受内存限制的工作线程中进行，30 秒超时。扫描版 PDF 需要 OCR；文档内嵌图片和复杂版式不由文本读取工具还原。

## 子人设

子人设与基础人设使用同一套指代：“你 / you”指模型，“用户 / the user”指使用者。内置人设遵循这个约定；升级不会改写用户自定义的人设文案。

人设文案的末尾留了一个模板槽——`…{{sub_persona}}`——由本 bundle 的运行时随行按会话从配置好的身份库里填入。两种选择方式，**都在模型的能动性之外**：

**选择器**——在新会话页面，选中「聊天模式」后，项目文件夹、模式右侧会出现同一行的子人设下拉。选择「基础人设」或配置好的身份；选中其他模式时隐藏该下拉。它复用原生模式选择的状态与菜单组件，不另占一行。选择器不要求开启开发者工具。选择会自动保存为新会话默认人设。进入会话后，输入框原权限下拉的位置显示人设下拉，可直接选择下一轮回答的人设；其他模式仍显示权限下拉。

**斜杠命令**——执行完全不经过模型：

```
/persona            # 列出可用子人设，并标出当前身份
/persona 面试官      # 切换（支持精确、大小写不敏感、唯一子串匹配）
/persona -          # 回到基础人设
```

新会话默认选择和每个会话的选择分别持久化，重启后恢复。切换从下一轮回答起生效；已经生成中的回答及其工具续接保持原人设。因此两个会话可以同时持有两个不同身份。工具能力不受影响——身份属于提示词工程，不是模型自己决定要不要调用的能力。

在人设下拉中选择「管理人设…」，即可新增、编辑、重命名和删除子人设。重命名保留现有选择；删除后，使用它的默认选择和会话从下一轮回到基础人设。保存失败会显示错误；多个窗口同时编辑时会提示重新载入，避免覆盖他人的修改。

内置身份来自 `chat-personas` 行的 `config.personas`，界面保存的修改优先。人设库和选择通过 Harness 的 `storageDomain` 原子保存，独立于安装包；更新 bundle 不会清空它们。该行挂在 profile 层，由 `config.presets` 限定生效范围（默认仅 `chat`）。选择器是本 bundle 的 Web 半区（`lib/client.js`，经 `dsh.client` 声明），通过 `/api/chatPersonas/*` 这条 Connection RPC 通道与 node 半区通信。

会话选择采用原生的逐条存储，保存时不再重写整张会话表。旧选择在首次加载时迁移，中断后可安全重试。启动时只清理在原生实时与持久化存储中均确认不存在的会话记录；归档会话及读取失败的记录保留。删除自定义人设不再留下删除标记；内置人设保留屏蔽标记，防止重新出现。菜单在打开、窗口获得焦点及修改成功后刷新，保留有限的启动重试，不进行常驻轮询。

`dsh.client.inject` 描述客户端插件图中的依赖边，不等同于每个 `require`。本 bundle 保留原生的空声明，React、React DOM 和基础控件沿用 Host 提供的基础模块；所需 Cordis 服务由客户端导出中的注入声明管理。不把普通模块名随意添加为插件依赖边，以免改变启动顺序。

## 验证

在本仓库执行 `npm ci`、`npm test`。测试使用本地 React/jsdom 依赖及已安装 Desktop 的运行时与 Conversation 构建产物，不依赖其他源码 checkout。其他安装位置可通过 `DSH_TEST_EXECUTABLE` 和 `DSH_TEST_RUNTIME` 指定；缺少 Desktop 运行时会明确报错。

## 自定义

修改 `cordis.patch.yml`（人设文案、子人设库、工具列表、`order`、`description`），重新打包安装即可。行 id `preset-chat` 是 Loader 编辑地址；`config.id: chat` 是写入会话的标识符。

## 说明

- `lib/index.js` 通过每个 agent context 的**服务注入子上下文**——`agent.ctx.inject(['systemPrompt', 'commands'], …)`——注册 `sub_persona` 提示词变量与 `/persona` 命令，因此两者都限定在单个 agent 作用域内。读取 agent 工厂上下文**未声明**的服务会**抛错**而不是返回 `undefined`，而可选链挡不住会抛错的 getter；0.7.0 正是踩了这一点，导致所有模式的会话创建与恢复一起失败。
- 提示词变量的 provider 若返回 `undefined`，会导致引用它的整个 section 渲染失败，所以除逐 agent 的提供者外，还注册了一个返回 `''` 的全局兜底提供者。
- 需要带 agent-preset 体系（`@deepseek-ai/dsh-agent-preset`）的 DSH profile。

## 许可

MIT
