Skip to content
dsh.fish
Bundle

dsh-per-turn-isolation

每轮对话独立:每轮开始前把本轮之前的历史从模型可见上下文中折掉,并按会话注入一段长期有效的设定。

Source
xinshang777
License
MIT
Updated
Updated 18 days ago

Readme

# dsh-per-turn-isolation ![version](https://img.shields.io/badge/version-0.1.0-blue) ![host](https://img.shields.io/badge/DSH-%40deepseek--ai%2Fdsh%20%7C%20Node%20%E2%89%A518-7C3AED) ![license](https://img.shields.io/badge/license-MIT-lightgreen)

> 一句话定位:它把一条越滚越长的对话,变成"每轮各自独立"的问答——历史自动折叠、规则每轮重注,给用 DSH 跑批量重复任务、又心疼上下文费用的人用。

![改造前 / 改造后](https://raw.githubusercontent.com/xinshang777/dsh-per-turn-isolation/main/docs/before-after.gif)

| 项 | 改造前 | 改造后 |
| --- | --- | --- |
| 模型看到的上下文 | 本轮 + 之前**所有**轮 | 本轮 + 你设定的长期规则 |
| 上下文占用 / 费用随轮数 | 一路增长 | 每轮基本恒定 |
| 每轮规则一致性 | 靠历史里残留的指令,容易漂 | 每轮强制注入,稳定 |
| 界面上能否回看历史 | 能 | 能(会话树完整保留,只是模型看不见) |
| 新会话默认状态 | — | 关闭(没有记录 = 未开启) |
| 需要模型记住整段对话时 | 正常工作 | 会"失忆",需手动关掉 |

## 适合谁 / 不适合谁

- 适合:如果你要**在同一个会话里连续问几十轮**、希望**每一轮都严格遵守同一份规则**(人设、代码规范、输出格式、回答语言)、在意**上下文占用与费用**不随轮数上涨,或者在"同一要求反复换输入"的批量任务(DSH 是什么,见[安装](#安装)的前置条件表)。
- 不适合:如果你需要模型**记住整段对话**(逐步排查 bug、多轮追问细节、边聊边改需求)——开着它模型会"失忆",请保持关闭;如果你只是偶尔问一两句,本来也没有上下文堆积问题,建议不装。
- 本插件**不做**:不删除聊天记录(历史始终完整保留,只是对模型不可见);不修改 DSH 源码;不替代 `/compact`;不做跨会话的记忆或摘要。

## 安装

### 前置条件

| 项目 | 要求 | 说明 |
| --- | --- | --- |
| Node.js | **≥ 18** | DSH 自身的运行要求 |
| pnpm | 较新版本即可 | `dsh plugin` 的剩余参数会**原样转发给 pnpm**,所以 pnpm 必须可用;缺它先 `npm i -g pnpm` |
| DSH | 已安装 `@deepseek-ai/dsh`,且能跑起 `dsh web` | 本插件的宿主。DSH(DeepSeek Harness)是一个**跑在你自己电脑上**的 AI 工作台,自带网页界面,可用插件扩展——插件就是给它加功能的包,装完重启界面上就多出对应按钮或页面 |
| `@deepseek-ai/cordis` | **^4.0.1** | 本插件的 peerDependency,随 DSH 一并安装,一般无需单独处理 |
| 操作系统 | Windows / macOS / Linux | 插件无平台特有代码 |

### 该选哪种方式

| 方式 | 适用场景 | 代价 |
| --- | --- | --- |
| GitHub 直装 | 只想用,不改代码 | 更新可能滞后 |
| `link:` 本地开发 | 调试 / 改源码 / 提 PR | 需 Node + 构建环境 |
| Download ZIP | 离线 / 锁版本 | 不自动更新 |

<details><summary>方式一:GitHub 直装(推荐,完整步骤)</summary>

```bash
dsh plugin --profile web add github:xinshang777/dsh-per-turn-isolation
```

安装成功后 `dsh plugin` 会把包登记进 profile 的 `dsh.profile.bundles`,**不需要手动改配置**。

</details>

<details><summary>方式二:link: 本地开发(改源码 / 提 PR 走这条)</summary>

```bash
git clone https://github.com/xinshang777/dsh-per-turn-isolation.git
cd dsh-per-turn-isolation
dsh plugin --profile web add link:.
```

用 `link:` 装的好处:改完源码**不用重装**,重启 `dsh web` 就生效,可以直接改 `lib/index.js`(宿主侧)与 `client.js`(浏览器侧)验证行为。

</details>

<details><summary>方式三:Download ZIP(离线 / 锁版本)</summary>

1. 打开仓库页面 → **Code → Download ZIP**,解压到任意目录;
2. 在该目录执行 `dsh plugin --profile web add link:.`(ZIP 解压出来就是源码,所以用 `link:` 挂上去);
3. 重启 `dsh web`。

⚠️ 此方式不会自动更新,升级要重新下载。

</details>

### 重启说明

| 场景 | 是否需要重启 |
| --- | --- |
| 新装 / 卸载本插件 | ✅ 必须完整重启 `dsh web` |
| 改 `~/.dsh/profiles/web/cordis.patch.yml` | ✅ 需要 |
| 改 `link:` 安装的插件源码(`lib/`) | ✅ 需要 |
| 在界面上开 / 关「独立」、改长期规则 | ❌ 不需要,即时生效 |
| 只是聊天、切会话 | ❌ 不需要 |
| 浏览器里看不到按钮 | 先 **硬刷新**(`Ctrl + F5`),注入脚本随页面加载 |

> 💡 装了 [dsh-restart-button](https://github.com/xinshang777/dsh-restart-button) 的话,点界面上的重启按钮一步到位,不用回命令行。

### 三十秒验证成功

打开 `dsh web` → 看到**输入框发送区「回退」按钮左边多了一个「独立」开关** = 装好了。

```mermaid
flowchart LR
    A["输入框"] --> B["「独立」开关<br/>本插件注入"]
    B --> C["「回退」"]
    C --> D["发送"]
```

如果没看到按钮,按顺序查三步:① `~/.dsh/profiles/web/package.json` 的 `dsh.profile.bundles` 数组里有没有 `dsh-per-turn-isolation`;② 是否**完整重启**了 `dsh web`(不是刷新页面);③ 浏览器硬刷新。

## 使用教程

1. **找到开关** —— 它被注入在输入框发送区,位于「回退」按钮的左边。
2. **设置长期规则并开启** —— 点击「独立」→ 弹出对话框 → 在输入框里写你想让模型"永远记住"的那段话(**可以留空**)→ 点「保存并开启」。
3. **验证隔离生效** —— 连续问两轮:

   ```text
   第 1 轮:记住一个数字:42
   第 2 轮:我刚才让你记的数字是多少?
   ```

   开启时第 2 轮**答不出来**——因为第 1 轮已被折叠,这正是隔离生效的表现;而界面上你仍能往上翻到第 1 轮。

4. **关闭** —— 再点一次「独立」→ 对话框里点「关闭功能」。对话框再次打开时,若功能已在运行,里面会显示「关闭功能」按钮。
5. **长期规则可以这么写** —— 按会话保存,换会话互不影响。

| 场景 | 长期规则示例 |
| --- | --- |
| 代码审查 | `只评审 diff,输出格式固定为:问题 / 原因 / 建议改法。不要复述代码。` |
| 批量改写文案 | `每条输入都按"标题 20 字内 + 正文 80 字内"重写,保持原意,不要加 emoji。` |
| 结构化抽取 | `把输入当作文本抽取字段,只输出 JSON,不要解释。` |

```mermaid
flowchart TD
    A["一轮开始"] --> B{"step === 1 ?"}
    B -->|"是"| C["把之前所有可见节点<br/>折叠成一个占位节点"]
    C --> D["写回会话树<br/>界面历史仍可见"]
    C --> E["注入长期规则<br/>到系统提示"]
    D --> F["模型只看到<br/>本轮 + 长期规则"]
    E --> F
    B -->|"否"| G["不干预"]
```

**配置项**

本插件在 bundle 层**没有可配置项**(`cordis.patch.yml` 里没有任何 `config:`),全部设置都来自界面上的对话框,并按会话落盘。状态文件里的字段如下:

| 名称 | 类型 | 默认值 | 是否必填 | 作用 |
| --- | --- | --- | --- | --- |
| `sessionId` | string | 当前会话 ID | 否 | 状态保存的键,插件与页面自动填充,**不要手改** |
| `enabled` | boolean | `false` | 否 | 该会话是否开启「每轮独立」 |
| `rule` | string | `""` | 否 | 每轮注入的长期规则文本;留空则只折叠历史、不注入规则 |

> **没有记录 = 关闭 + 规则为空**,所以新建会话默认是关闭状态。

## 常见问题 / 排障

**1|开启后模型"失忆",答不上之前的内容**

- **原因**:**这是设计目标,不是 bug**。本轮之前的历史已被折叠成一个占位节点,模型确实看不到。
- **处理**:需要模型记住上下文时,再点一次「独立」→「关闭功能」即可。

**2|输入框旁边没有「独立」开关**

- **原因**:三种可能——插件没被挂载进 profile、进程没完整重启、页面是旧缓存。
- **处理**:① 打开 `~/.dsh/profiles/web/package.json`,确认 `dsh.profile.bundles` 数组里有 `dsh-per-turn-isolation`;② **完整重启** `dsh web`(不是刷新页面);③ 浏览器硬刷新 `Ctrl + F5`。

**3|关闭功能后,更早的轮次还是没有回到模型上下文**

- **原因**:**预期行为**。已折叠的更早轮次不会回溯恢复——折叠是写入会话树的动作,不是临时开关。
- **处理**:想恢复完整上下文,开一个新会话,或依赖 DSH 自身的 `/compact` / 会话管理。**上一轮**会正常保留。

<details><summary>完整排障表</summary>

| 现象 | 处理 |
| --- | --- |
| 开启后模型失忆 | 设计目标。需要记忆时关掉即可。 |
| 关闭后更早轮次没回来 | 预期行为:折叠已写入会话树,不回溯。上一轮会保留。 |
| 输入框旁没有「独立」按钮 | ① 查 `bundles` 是否含本插件;② 完整重启 `dsh web`;③ 硬刷新 `Ctrl + F5`。 |
| 按钮在,但点了没反应 | 打开 `GET /dsh-per-turn/debug` 看当前会话状态;确认浏览器控制台无报错。 |
| 换个会话后设置没了 | 规则**按会话**保存,不同会话互不影响;新建会话默认关闭。 |
| 长期规则看起来没生效 | 规则走系统提示注入,在每轮 `step === 1` 时生效;确认对话框里点的是「保存并开启」而不是只保存。 |
| 状态文件损坏 / 想重置 | 关闭 `dsh web`,删除 `$DSH_HOME/.dsh-per-turn-isolation.json`(默认 `~/.dsh/.dsh-per-turn-isolation.json`),重启即可回到"全部会话均关闭"。 |

</details>

## 兼容性与已知限制

- **宿主最低版本**:Node.js **≥ 18**;peerDependency `@deepseek-ai/cordis` **^4.0.1**;需要能跑起 `dsh web` 的 DSH。
- **平台差异**:无。插件不含平台特有代码,Windows / macOS / Linux 行为一致。
- **冲突插件**:未发现硬冲突。但需要留意——任何**依赖"本轮之前的完整历史"**的功能,与本插件同开时行为会受影响,因为历史确实已被折叠。若你已经装了上下文压缩 / 摘要类插件,建议先单独验证两者叠加后的表现。
- **已知限制**:只做"折叠 + 注规则",不理解语义;折叠不可回溯;规则文本会随系统提示发给模型服务商(见[隐私](#隐私))。

## 升级、卸载与数据

- **配置存放位置**:`$DSH_HOME/.dsh-per-turn-isolation.json`(默认 `~/.dsh/.dsh-per-turn-isolation.json`),按 `sessionId` 保存 `enabled` 与 `rule`。除此之外不在任何位置落盘。
- **升级**:
  - `github:` 方式:重跑一次 `dsh plugin --profile web add github:xinshang777/dsh-per-turn-isolation`,或指定版本 `...add github:xinshang777/dsh-per-turn-isolation#v0.1.0`;
  - `link:` 方式:`git pull` 后重启 `dsh web`。
- **干净卸载**:
  ```bash
  dsh plugin --profile web remove dsh-per-turn-isolation
  ```
  然后(可选)删除状态文件 `~/.dsh/.dsh-per-turn-isolation.json`。插件不写注册表、不写系统目录,删完无残留。
- **回滚**:`link:` 方式 `git checkout <上一个 tag 或 commit>` 后重启;`github:` 方式把版本号换成旧 tag 重装。状态文件与版本无关,不受影响。

## 隐私

- **数据是否出本机**:**不出**。插件不发起任何外部网络请求,与页面之间只走**回环 HTTP 接口**(`/dsh-per-turn/*`)。
- **是否联网**:**不联网**。除 DSH 自身与模型的通信外,本插件没有任何外部连接。
- **是否读取账号**:**不读**。它既不看你的账号,也不读客户端的登录态文件;只读写 `$DSH_HOME` 下的那一个 JSON。
- **需要知情的一点**:你填写的**长期规则**会被注入系统提示,因此会随每一轮请求发送给模型服务商。请不要在规则里写密码、密钥或其他敏感信息。

## 实现原理(贡献者向)

<details><summary>挂钩点 · 数据流 · 接口表 · 目录结构</summary>

**挂钩点:`agent/pre-step`**

插件实现 DSH 的 `agent/pre-step` 事件,在**一轮的第一个步骤**(`step === 1`)拦截:

```js
ctx.on("agent/pre-step", async (payload, next) => {
  const { agent, messages, turn, step } = payload ?? {};
  const isTurnStart = step === 1 && Array.isArray(messages) && messages.length > 0;
  // …
});
```

**折叠:把历史压成一个占位节点**

在该轮开始前,插件把「本轮之前的所有可见节点」合成**一个 `user/message` 占位节点**,并用 `session.append("user/message", placeholder, …)` 写回会话树。

- 写回会话树 → 所以**界面上历史依然完整可见**;
- 折叠成单节点 → 所以**模型看不到更早的内容**。

**长期规则:走系统提示**

通过 `ctx.inject(["systemPrompt"], host => contribute(host))` 把长期规则贡献进系统提示,使它在每一轮稳定生效,而不是"靠历史里残留的一句指令"。

**接口表**

| 方法 | 路径 | 用途 |
| --- | --- | --- |
| `GET` | `/dsh-per-turn/state?sessionId=…` | 读取该会话的 `{ ok, sessionId, enabled, rule }` |
| `POST` | `/dsh-per-turn/state` | 写入 `{ sessionId, enabled, rule }`,返回同上 |
| `GET` | `/dsh-per-turn/debug` | 查看存储路径、当前会话状态、最近的折叠记录 |

**数据流**

```
一轮开始(step === 1)
  ├─ 读取状态文件:该会话 enabled?rule?
  ├─ enabled=true → 折叠历史为占位节点 → session.append 写回会话树
  └─ rule 非空    → ctx.inject(["systemPrompt"]) 注入
→ 模型只收到 [长期规则] + [本轮输入]
```

**状态存储**

```jsonc
// %USERPROFILE%\.dsh\.dsh-per-turn-isolation.json
{
  "sessions": {
    "<sessionId>": { "enabled": true, "rule": "…" }
  }
}
```

**目录结构**

```text
dsh-per-turn-isolation/
├── lib/index.js            # 宿主侧:pre-step 折叠 + 长期规则注入 + HTTP 接口
├── client.js               # 浏览器侧:注入「独立」开关按钮与设置对话框
├── cordis.patch.yml        # bundle 挂载声明(把插件插进 profile 配置树)
├── icon.svg                # 插件图标
├── locale/zh.json, en.json # 中英文文案
└── package.json
```

前端通过 `@deepseek-ai/dsh-client-ui-slots` 把按钮插进输入框发送区的插槽,并依赖 `@deepseek-ai/dsh-client-connection` 等客户端包与宿主通信。

</details>

详见 `docs/architecture.md`

## 贡献与反馈

[CONTRIBUTING.md](CONTRIBUTING.md) · [CHANGELOG.md](CHANGELOG.md) · Issue 模板

本地开发:把仓库 `link:` 进 profile 后改代码,重启 `dsh web` 即生效。

## 许可证

作者在 `package.json` 中声明为 **MIT**;但仓库目前**没有 `LICENSE` 文件**,GitHub 页面因此显示为"未指定"。建议补一个 `LICENSE` 文件以消除歧义。在此之前,如需使用请自行评估。

Install

dsh plugin --profile web add github:xinshang777/dsh-per-turn-isolation

Profile: web

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source