dsh-ai-lessons
给 DeepSeek Harness (DSH) 用的插件:把本地模型「犯蠢的时刻」记下来,并把教训自动带进之后的每一次对话。
命中一句话就能概括:记录犯过的错 → 沉淀成清单 → 下次开口前先看一眼。
仓库:https://github.com/dusktin71-lang/dsh-lessons · License: MIT
它做了什么
| 能力 | 说明 |
|---|---|
| 自动识别 | 你对 AI 说「你又搞错了」「不是这样的」「答非所问」这类短促不满时,自动记一条,并附上当时被纠正的回答要点 |
| 随手补录 | 说「记一下:动手删文件前先问我」或 /lesson xxx,直接入库 |
| 工具报错留档 | 可选:工具执行失败时也记一条(默认关闭,避免噪声) |
| 动态注入 | 通过 systemPrompt.section() 注册一段每次组装请求时实时求值的提示词 —— 记完立刻生效,不用重启 |
| 会话级开关 | 新开一个对话后,可以单独决定这个对话要不要带着历史教训;也可以在对话里直接说 /lessons off、/lessons on |
| 可视化面板 | DSH 右侧栏的「AI 教训」标签页:查看 / 停用 / 删除 / 补录,以及「注入预览」看模型实际读到了什么 |
注入到提示词里的样子:
## 历史教训(此前对话中被明确纠正过的问题)
下面是用户在过去的对话里明确表达过不满、或主动要求牢记的事项。
请在本次回答中主动规避这些错误;与当前任务无关的条目可以忽略,但不要重复犯同样的错。
- 避免重复这个问题:用户当时说“你又搞错了,我根本没让你删东西”。
· 当时用户的反应:“你又搞错了,我根本没让你删东西”
· 当时 AI 的回答要点:好的,我已经删除了那个文件夹。
- 动手删除文件前必须先问我
用法
自动(什么都不用做)
对 AI 表达不满即可,例如:
- 你又搞错了 / 不对 / 不是这样的 / 重新做一遍
- 你没理解我的意思 / 答非所问 / 胡说
判定刻意保持保守:只对 120 字以内、且命中明确负面词语的消息入库,避免把正常讨论误判成抱怨。误记了在面板里删掉就行。
手动
| 你输入 | 效果 |
|---|---|
记一下:<内容> / 记住:<内容> |
记一条教训 |
/lesson <内容> |
同上 |
/lessons off / /lessons on |
本对话停用 / 启用教训注入 |
面板
DSH 右侧栏 →「AI 教训」:
- 顶部开关:总开关、自动识别、工具报错、当前对话是否生效
- 教训:补录、逐条停用、删除
- 注入预览:模型实际会读到的原文
- 更多设置:注入条数上限、单条字符上限、新对话默认是否生效、清空全部
在哪找、怎么打开。 右侧栏是 DSH 里一列可以折叠的面板,宽度默认是 0,
而且这个宽度会持久化 —— 也就是说,插件把标签页注册进去并不会自动把那一列拉开。
DSH 的规则写在官方文档里:「面板展开,因为用户看不到的内容不算打开」。
所以有两条路:
- 不用管:插件首次加载后会自动展开右栏一次(
config.autoOpenPanel, 只做一次,之后记在state.panelAutoOpened里,永不再打扰)。 - 自己开:展开按钮在会话标题栏最右端(
conversation.session.header.corner席位),图标是一个分成左右两半的方块、右半带一条竖线 —— 就是左侧栏折叠图标的镜像。 收起后再点它就能拉回来。打开后若看到的是引导页,点其中的「AI 教训」入口即可。
文件位置
| 内容 | 路径 |
|---|---|
| 插件源码 | <本目录>(通过目录联接挂在 profile 下,改这里就是改生效代码) |
| 数据文件 | ~/.dsh/ai-lessons/state.json |
| 自检日志 | ~/.dsh/ai-lessons/boot.log(每次启动追加一行,用于排查加载问题) |
安装 / 卸载
git clone https://github.com/dusktin71-lang/dsh-lessons.git
cd dsh-lessons
node install.mjs # 安装(会备份 profile/package.json)
node install.mjs --no-install # 只改配置,不建链接
node verify-install.mjs # 校验安装结果
node selftest.mjs # 校验判定规则
node smoketest.mjs # 用假 ctx 跑通宿主半身
安装做了三件事:
- profile
package.json里加一条link:依赖; dsh.profile.bundles里登记dsh-ai-lessons;- 建立
node_modules/dsh-ai-lessons→ 本目录的联接(pnpm 可用时走 pnpm,否则用目录联接)。
改完代码后重启 DSH 生效。
卸载:删掉上面那两处登记,或把 package.json.bak-* 还原回去,即可。
实现说明
DSH 插件由两个半身组成,本插件都用官方约定的方式接入,没有额外构建步骤。
宿主半身 index.js
| 用到的能力 | 用途 |
|---|---|
ctx.on('session/event', (session, event) => …, { global: true }) |
监听全量会话事件:user/message、assistant/message、tool/result |
ctx.inject(['systemPrompt'], …) → systemPrompt.section({ name, order, text }) |
注册提示词段。text 传函数,每次组装请求时求值,所以「记录即生效」 |
systemPrompt.getSectionOrder('DEPLOYMENT_PERSONA_SUFFIX') + 1 |
段位置:排在部署人设之后 |
interpolate: false |
教训原文里可能出现 {{,必须关掉变量插值,否则会抛错 |
ctx.inject(['connection'], …) → connection.fetch.register({ path, methods, requestBody, fetch }) |
给面板用的本地接口 /api/dsh-ai-lessons/rpc |
ctx.get('dshHomePath') |
数据目录,拿不到时回退到 ~/.dsh |
任一服务缺失只会让对应功能降级(例如没有 HTTP 服务就没有面板),插件本体不会挂。
浏览器半身 client.js
按官方打包格式暴露:
window.__ModuleLoader__.load({ id: 'dsh-ai-lessons', factory: (req) => { … } })
req 用来取 react / react-dom / UI 原语。UI 用 React.createElement 直接写,不依赖任何构建工具;@deepseek-ai/dsh-client-ui-primitives 是可选依赖,取不到就全部退化成原生元素。
面板通过 ctx.inject(['sidebarRightTabs'], …) + ctx.slots.register('sidebar.right.pane.tab', …) 挂到右侧栏,数据和操作走上面那个本地接口。
注意:sidebarRightTabs 由 @deepseek-ai/dsh-client-ui-sidebar-right 提供,想让这个服务在浏览器侧存在,必须在 package.json 里声明:
"dsh": {
"client": {
"platform": "web",
"inject": [
"@deepseek-ai/dsh-client-connection",
"@deepseek-ai/dsh-client-locale",
"@deepseek-ai/dsh-client-ui-sidebar-right",
"@deepseek-ai/dsh-api-remotes"
]
}
}
inject 为空时插件不会报错,但服务永远不出现,ctx.inject(['sidebarRightTabs'], …) 的回调也就永远不会执行 —— 表现就是「右侧栏里找不到这个标签页」。
可调项
面板「更多设置」里可以直接改;也可以手改 ~/.dsh/ai-lessons/state.json 的 config:
| 字段 | 默认 | 含义 |
|---|---|---|
enabled |
true |
总开关 |
autoDetect |
true |
自动识别用户不满 |
recordToolErrors |
false |
工具报错也记一条 |
maxInject |
30 |
注入条数上限 |
maxLessonChars |
240 |
单条注入的字符上限 |
defaultSessionEnabled |
true |
新对话默认是否生效 |
includeSubagents |
false |
预留:是否注入子智能体 |
autoOpenPanel |
true |
首次加载后自动展开右栏一次(只做一次,标记记在 state.panelAutoOpened) |
排查:右侧栏没有「AI 教训」标签页
两个半身是独立加载的:宿主半身跑在 Node 侧,浏览器半身由 DSH 的 web app 单独加载。面板不出现时,先看自检日志:
tail -20 ~/.dsh/ai-lessons/boot.log
日志每行一个 JSON,看 event 字段:
| event | 含义 |
|---|---|
apply |
宿主半身被加载了。没有这一行 → 插件根本没起来(检查 profile 的 package.json 里 dsh.profile.bundles 有没有登记) |
section |
系统提示词注入的注册结果,ok:false 时看 reason |
route |
面板所用 HTTP 接口的注册结果 |
client-report |
浏览器半身的上报,具体看它里面的 payload.event |
client-report 的 payload.event 取值:
| 值 | 含义 | 怎么办 |
|---|---|---|
client/apply |
浏览器半身开始执行 | 说明它被加载了 |
client/tab-registered |
标签页注册成功 | 面板已进右侧栏的标签仓库 |
client/sidebar-service-missing |
4 秒内始终没拿到 sidebarRightTabs |
dsh.client.inject 里缺少 @deepseek-ai/dsh-client-ui-sidebar-right |
client/register-failed |
注册时抛错 | 看 reason |
client/auto-opened |
首次自动展开右栏成功 | 正常 |
client/auto-open-skipped |
重试若干次仍未展开 | 多半是当时没有挂载中的会话;手动点展开按钮即可 |
注册成功却看不到面板,八成是右栏本身处于折叠态 —— 注册和展开是两件事, 见上文「面板 → 在哪找、怎么打开」。
最常见的坑:dsh.client.inject 为空不会报任何错,只会让 sidebarRightTabs 永远不出现,于是 ctx.inject(['sidebarRightTabs'], …) 的回调永不执行 —— 表现就是「右侧栏里找不到这个标签页」。改完 package.json 必须重启 DSH。
No comments yet. Be the first to write one.