Bundle
dsh-feedback-combined
Combined DSH feedback plugin: host-side bridge (collect ratings, persist JSONL, inject summary, query tools) + client-side one-click thumbs up/down UI in a single package.
- Source
- zhuran305-web
- stars
- 1 stars
- Updated
- Updated 4 days ago
Readme
# dsh-feedback-combined — DSH 反馈整合插件(单包双端)
> 一个包补齐 DSH(DeepSeek 桌面端)反馈闭环:
> **客户端侧**一键 👍/👎 即时评分 UI + **宿主侧**评分采集 → JSONL 落盘 → 请求前注入摘要 → 查询工具。
> 配套 [feedback-adapter](#feedback-adapter-skill) Skill 约束模型收到负反馈后的反思与调整行为。
DSH 官方反馈体系设计为「评分仅写日志、不进模型上下文」,用户点 👎 后模型在下一轮完全不知道被否决过。本插件补上这一环,且把 UI 与数据层整合为单包:用户只需安装一个插件。
## 仓库结构
- 仓库根即主插件 `dsh-feedback-combined`(单包双端,推荐安装),可用 `pnpm add github:zhuran305-web/dsh-feedback-combined` 直接安装;
- [`legacy/`](./legacy/) 保留整合前的两个单端插件(`dsh-feedback-bridge` 仅宿主侧、`dsh-simple-feedback-ui` 仅客户端侧),仅供维护参考,无需安装;
- 两个单端插件功能已完全并入主插件(详见各自 README)。
## 单包双端结构
本插件同时声明 DSH 的两套正交配置,一个包同时携带宿主半边与客户端半边:
| 半边 | 加载机制 | 功能 |
|---|---|---|
| 宿主侧(Node) | `dsh.bundle.patch`(cordis.patch.yml) | 采集评分事件 → JSONL 落盘 → `agent/pre-step` 注入摘要 → 注册查询工具 |
| 客户端侧(web) | `dsh.client.inject`(platform: web) | slot shadow 遮蔽官方按钮,单击即时提交、长按官方弹窗、Deepy 联动 |
两半边**无直接调用**:客户端经官方 `messageFeedback.put` 提交评分,宿主侧经官方 `session/event` 事件通道采集,slot/事件空间正交。
> 宿主入口为 `lib/index.js`(exports `"."`),客户端产物为 `lib/client.js`(exports `"./client"`)。
> **客户端 bundle 内嵌的 `__ModuleLoader__.load` 注册 id 必须等于包名**,否则激活审计报 `loaded without registering`。
## 安装与启用
前置:DSH 已安装;profile 目录可写;profile 的 `pnpm-workspace.yaml` 含 `nodeLinker: hoisted`。
```bash
# 1. 安装插件包进 profile(路径含空格,必须加引号)
cd "C:/Users/chichen/.dsh/profiles/desktop"
pnpm add "file:D:/GongJu12345/airuanjian/zcode/dsh judge/dsh-feedback-combined"
```
```bash
# 2. 部署 feedback-adapter Skill
mkdir -p "C:/Users/chichen/.dsh/skills/feedback-adapter"
cp "D:/GongJu12345/airuanjian/zcode/dsh judge/dsh-feedback-combined/SKILL.md" \
"C:/Users/chichen/.dsh/skills/feedback-adapter/SKILL.md"
```
然后在 profile `package.json` 的 `dsh.profile.bundles` 数组追加 `"dsh-feedback-combined"`:
```json
{
"dsh": {
"profile": {
"bundles": ["...已有插件...", "dsh-feedback-combined"]
}
}
}
```
**若此前安装过旧插件 `dsh-feedback-bridge` / `dsh-simple-feedback-ui`,必须先移除**,否则双份挂载(重复采集 + 重复 slot 注册):
```bash
cd "C:/Users/chichen/.dsh/profiles/desktop"
pnpm remove dsh-feedback-bridge dsh-simple-feedback-ui
```
最后完全退出 DSH(含托盘)再启动。
**源码更新后刷新**(file: 依赖是复制,改源码不会自动生效):
```bash
cd "C:/Users/chichen/.dsh/profiles/desktop" && pnpm install
```
再重启 DSH。改 SKILL.md 后需重新执行上面的 `cp` 命令同步到部署目录。
## 启用确认
重启后通过以下方式确认加载成功:
- 宿主侧:日志出现 `feedback-combined: activated, data file: ...`;
- 数据目录已建立(首次评分后):`~/.dsh/data/dsh-feedback-combined/feedback.jsonl`;
- 客户端侧:点击 👍/👎 即时提交且 toast 出现;UI 无 `Failed to load plugins` 报错;
- 历史数据自动迁移:旧 `~/.dsh/data/dsh-feedback-bridge/feedback.jsonl` 存在且新文件不存在时,启动时自动复制(幂等)。
## 配置项
### 宿主侧(通过 DSH 设置界面或配置文件,均为 volatile)
| 配置项 | 默认 | 说明 |
|---|---|---|
| `injectEnabled` | `true` | 关闭后完全不注入(采集与工具照常) |
| `maxItems` | `3` | 注入摘要最多聚合几条负反馈 |
| `maxCharsPerItem` | `200` | 单条摘要字符上限 |
| `maxTotalChars` | `600` | 注入总字符上限(含固定前后缀) |
| `reconcileEnabled` | `true` | 对账兜底开关(`messageFeedback.list()` 不可用时可关) |
### 客户端侧(通过 DSH 设置界面,均为 volatile)
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
| `toastEnabled` | boolean | `true` | 关闭后仅图标状态变化作为确认 |
| `toastDurationMs` | number | `2000` | toast 显示时长(1500–3000ms) |
| `toastPositiveText` | string | `"赞已记录"` | 正向 toast 文案 |
| `toastNegativeText` | string | `"反馈已记录,AI 将改进"` | 负向 toast 文案 |
| `longPressDialogEnabled` | boolean | `true` | 关闭后长按无效,仅响应单击 |
| `duplicateClickStrategy` | enum | `"open-dialog"` | 重复点击策略:`open-dialog` 或 `cancel` |
## 交互说明(客户端侧)
| 操作 | 效果 |
|---|---|
| 单击 👍 | 即时提交 positive 评分,图标高亮,toast 显示正向文案 |
| 单击 👎 | 即时提交 negative 评分,图标高亮,toast 显示负向文案 |
| 长按 👍/👎 | 唤起官方完整反馈弹窗(7 分类 + 备注 textarea) |
| 再次点击相同评分 | 按策略:唤起弹窗(默认)或取消评分 |
Deepy 联动:向 dsh-claude-style 的像素小鲸鱼合成 pointer 事件(happy → 连按 4 次挠痒笑;panic → 拖动慌乱循环)。鲸鱼不在场时静默跳过。
## slot 契约记录
| 字段 | 值 |
|---|---|
| slot name | `conversation.chat.assistant-actions` |
| slot id | `feedback` |
| priority | `-1`(官方为 `0`,lowest renders) |
| order | `10`(与官方一致) |
| 遮蔽范围 | 仅按钮区域,不遮蔽弹窗 |
## 数据格式
数据文件:`~/.dsh/data/dsh-feedback-combined/feedback.jsonl`(append-only JSONL,永不改写已写入的行)。
文件首行为 `#` 注释,说明格式。每行一个 JSON 对象,四种行类型:
| 行类型 | 字段 | 说明 |
|---|---|---|
| `rating` | `ts, kind, sessionId, messageId, rating, category?, note?, version, source, scope` | 一次评分事件(👍/👎) |
| `consumed` | `ts, kind, sessionId, messageId, rating, version?` | 该反馈已注入一次(消费标记) |
| `deleted` | `ts, kind, sessionId, messageId` | 官方 delete 事件的 tombstone(撤回) |
| `recorded` | `ts, kind, sessionId, note, rating?, scope` | `record_feedback` 工具写入的观察 |
**字段说明:**
- `rating`:`"positive"`(👍)或 `"negative"`(👎)
- `version`:随机 UUID,每次实质 put 更新;去重键 = `sessionId + messageId + version`
- `source`:事件来源(`session/event` / `feedback/committed` / `reconcile`)
- `scope`:采集时归属作用域
**三种作用域:**
| 作用域 | 含义 | 查询聚合范围 |
|---|---|---|
| `session` | 该记录的 sessionId 本身 | 仅当前会话 |
| `task` | 该 sessionId 沿 `parentSession` 上溯到的根会话 | 根会话及其全部子会话 |
| `global` | 全部记录 | 跨所有会话 |
**消费语义:** 带 `version` 的 consumed 只挡同版本评分(用户对同一消息再次 👎 会产生新 version,可再次注入);不带 version 的 consumed 永久挡住该 messageId(兼容旧语义)。
## 提供的工具(宿主侧,agent 作用域)
### `record_feedback`
补记一条反馈观察到本地日志。当用户口头给出评价但没有点 👍/👎,或需记录复盘结论时调用。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `note` | string | 是 | 要补记的内容(≤500 字符) |
| `rating` | string | 否 | `positive` 或 `negative`,仅观察时可省略 |
| `scope` | string | 否 | `session`(默认)或 `global` |
### `get_recent_feedback`
查询历史评分与反馈记录,按时间倒序返回。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `scope` | string | 否 | `session`(默认)/ `task` / `global` |
| `limit` | integer | 否 | 返回条数,默认 5,上限 20 |
| `rating` | string | 否 | 按评分过滤:`positive` 或 `negative` |
| `includeNotes` | boolean | 否 | 是否含备注原文,默认 false |
## 联调验证
| # | 验证项 | 操作 | 预期 |
|---|---|---|---|
| V1 | 双端挂载 | 重启后检查 | 宿主日志 `feedback-combined: activated`;UI 一键评分即时提交 |
| V2 | 负反馈闭环(核心) | AI 回答 → 点 👎 → 下一轮 | (a) 注入 `[用户反馈] ... 👎`;(b) 加载 feedback-adapter;(c) 针对性调整非空洞道歉;(d) 同一 messageId 未重复注入 |
| V3 | 正反馈记录 | 点 👍 | JSONL 有记录、`get_recent_feedback` 可查到、下一轮未注入正文 |
| V4 | 作用域 | session/task/global 调用工具 | 三种作用域读写过滤正确 |
| V5 | 长度与污染 | 连续 3 轮普通对话 | 无负反馈轮次零注入;有注入时 ≤600 总 / ≤200 单条 |
| V6 | 鲁棒性 | 反馈服务不可用 / JSONL 写失败 | 对话正常进行、日志有 warn 但无阻断 |
| V7 | 长按弹窗 | 长按 👍/👎 | 官方完整弹窗(7 分类 + 备注) |
| V8 | 数据迁移 | 有旧 feedback.jsonl 时首次启动 | 数据自动复制到新目录,历史可查 |
## 故障排查
| 症状 | 排查方向 |
|---|---|
| UI 报 `Failed to load plugins` / `loaded without registering` | client.js 内嵌注册 id 是否等于包名(激活审计要求两者一致)→ 重新打包统一 id |
| 插件加载失败 | profile `package.json` 是否被写坏 → 恢复备份;`dsh.profile.bundles` 是否含 `"dsh-feedback-combined"` |
| 注入未出现 | ① `session/event` 是否捕获到 `feedback/message-put`;② JSONL 是否写入成功(查日志 warn);③ consumed 是否误判;④ `agent/pre-step` 是否挂载(根 agent 判定) |
| 重复注入 | consumed 行是否落盘(注入后立即写 consumed,若写失败会重复注入——查日志) |
| 写入失败 | `withFileLock` 冲突视为暂时失败,下次事件重试;所有写操作 try/catch 静默降级,不阻断对话 |
| Skill 未触发 | 确认 `~/.dsh/skills/feedback-adapter/SKILL.md` 已部署且 frontmatter `name: feedback-adapter` |
| 依赖解析失败 | profile `pnpm-workspace.yaml` 是否含 `nodeLinker: hoisted`(peer 依赖由运行时宿主树提供) |
## 已知限制
- **R4 consumed 时序窗口**:pre-step 注入成功后立即落 consumed,若该 step 随后被 abort,注入内容丢失但已标消费。
- **R7 子 agent 不注入**:注入只挂根 agent,子会话(subagent)回合不注入反馈摘要。
- **长按唤起弹窗**依赖访问 slot 内部记录,若 DSH 版本变更 slot 内部结构可能失效,失效时长按降级为无操作,一键评分不受影响。
- 客户端 bundle 是预打包产物,修改 UI 逻辑需重新打包并保证内嵌 id 与包名一致。
## feedback-adapter Skill
配套 Skill 位于 `SKILL.md`,定义模型收到负反馈后的反思与调整规范。安装时复制到 `~/.dsh/skills/feedback-adapter/SKILL.md`。详见 [SKILL.md](./SKILL.md)。
Install
dsh plugin --profile web add github:zhuran305-web/dsh-feedback-combined
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-feedback-combined from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.