dsh-prompt-inject
DeepSeek Harness(下称 dsh)的一个插件:在设置页里,按注入点和模型配置要注入的提示词。
装好之后,设置里多出一项「dsh-prompt-inject」。里面是八个注入点,每个注入点下可以写若干条规则,规则可以自己排序、单独开关、指定对哪些模型生效。改完自动保存,下一轮对话就用新值。
- 目标适配最新 dsh rc 版本
- 注入的位置分两类:夹在用户输入前后的前置词 / 后置词,和 system 里的六个位置。
- 规则正文里可以写
{{model}}这类模板字段,发送时换成这次请求的真实值。 - 配置文件存在本地,一切改动只影响你自己。
安装
用 dsh 自带的插件命令,从 GitHub 装进你要用的 profile:
dsh plugin --profile web add github:XuezuoYS/dsh-prompt-inject
--profile 填你平时启动的那个 profile:命令行跑 Web UI 就是 web,桌面版是 desktop。第一次用某个 profile 时,这条命令会先把它初始化出来。新装的组合包默认就是启用的,不用再开。
命令里的 github:所有者/仓库 是 pnpm 的简写形式,转发给 profile 目录里的 pnpm 执行。想把版本钉死、不让后面的推送悄悄改变实际跑的代码,就带上 commit:
dsh plugin --profile web add github:XuezuoYS/dsh-prompt-inject#<sha>
本插件是纯 JS 的手写包,没有 prepare、install 之类的构建脚本,所以官方文档里 git 安装那道「授权构建」的坎在这里不存在(pnpm 10 只会拦带构建脚本的包)。
装完如果 dsh 正在跑,命令会告诉你是立刻生效还是要重启;重启一次 dsh 总是安全的。想确认装上了,可以只看配置层而不启动:
dsh --profile web --dump-config # 应该能看到一行 "# == dsh-prompt-inject"
卸载会同时移除依赖和它贡献的配置层:
dsh plugin --profile web remove dsh-prompt-inject
卸载不动你的规则文件,重装回来配置还在。
设置页里怎么用
打开 设置 → dsh-prompt-inject,页面分两组:用户消息(前置词 / 后置词)和 system 段。system 组里各项按它们在 system 里出现的先后排列。
每个注入点是一个可折叠的小节,有规则的默认展开,点一下能收起。小节里点「+ 新增规则」写一条;同一个注入点的多条规则按列表从上到下的顺序拼接,中间空一行;不同注入点互不影响。
一条规则卡片上有这些东西:
| 元素 | 干什么 |
|---|---|
| 开关 | 关掉就不参与拼接,规则仍留在列表里 |
| 模型作用域 | 一枚胶囊,写着 全部 / 已应用 N 个模型 / 无应用,点它开弹层多选 |
| 文本 | 多行输入框,就是要注入的内容 |
| ↑ / ↓ | 调整这一注入点内的顺序,首尾自动禁用 |
| ⧉ | 复制一份,插在它后面 |
| ✕ | 删除,删完 Toast 里给一次「撤销」,撤销会放回原来的位置 |
编辑后 500ms 自动提交,顶部一直显示保存状态:保存中 / 已保存 / 保存失败。没有单独的保存按钮,也不需要。写失败或版本对不上时会回滚到服务端的最新值并提示你,不会静默覆盖。
注入点
| 设置页里叫 | 键 | 怎么进 system | 落在哪 |
|---|---|---|---|
| 用户前置词 | user.prefix |
插入一条独立 user 消息 | 当前用户输入之前,每轮只注入一次 |
| 用户后置词 | user.suffix |
同上 | 当前用户输入之后,每轮只注入一次 |
| system 头部 | system.personaHead |
替换官方那一段 | 官方 persona 头(order 0),官方用来说明当前调用的模型名 |
| 工具说明之前 | system.beforeTools |
新加一段 | order 950 |
| 工具说明之后 | system.afterTools |
新加一段 | order 3050 |
| 结构化输出之前 | system.middle |
新加一段 | order 9800 |
| system 尾部 | system.personaTail |
替换官方那一段 | 官方 persona 尾(order 10200),官方用来说明当前工作目录 |
| system 最末尾 | system.last |
新加一段 | order 10300,排在官方 persona 尾之后 |
system 那六项的两种做法分得很清楚:
- 替换(头部 / 尾部):官方本来就有这两段,插件只把它们的内容换掉。没配规则的那一侧原样不动,还是官方默认。官方段不会因为替换而消失,也不会出现重复的两段。
- 新加一段(工具说明之前 / 之后、结构化输出之前、system 最末尾):官方没有对应段落,插件在指定段位加一段。没配规则时这段解析成空串,官方渲染时会直接丢掉,等于不存在,不会覆盖任何官方内容。
细节和官方段位参照表见 docs/injectPoint.md。
用户前置词 / 后置词注入之后不会清除
这是设计决定,也是这个插件唯一需要你提前知道的代价,所以设置页在这两个小节顶部就写着这句话。
- 注入的前后缀是独立的 user 消息,和你打的原文一样落进会话历史,之后每一轮模型都还能读到。
- 注入之后就再也不动它:不压缩、不清除、不还原,一轮一轮累积下去。
- 上一轮的后置词会夹在上一轮原文和这一轮输入之间,模型很可能把它当成「用户又补充了一句」。所以这两个位置只写不会引起误解的话,比如统一的输出要求;别写带时间、带轮次、带指代的内容。
- 你发的那条消息本身不会被改动,聊天记录仍然是真的。
- 到 provider 那边,连续的 user 消息会被 DeepSeek adapter 合并成一条
{前置词}原文{后置词};pi-ai 那类不合并,就是三条。 - 一轮只注入一次:工具循环的续跑请求不会再加一遍,本轮那份继续保持。
想让某句话只在 system 里生效、不留在用户历史里,就配到 system 那几个注入点去。
模板字段
规则正文里可以写这几个字段,发送时会换成这次请求的真实值:
| 字段 | 换成什么 |
|---|---|
{{model}} |
这次请求实际用的模型 id,比如 deepseek-flash |
{{provider}} |
这个模型属于哪个 provider,比如 deepseek-account |
{{cwd}} |
当前会话工作目录的绝对路径 |
它们就是官方 persona 头尾在用的那三个变量,值来自官方 agent 循环。设置页每个展开的小节顶部都有一行字段徽章,点一下就把 {{字段}} 插到最近编辑那条规则的光标处;一条规则都没有时,点徽章会直接新建一条。
渲染时机:
- 用户前置词 / 后置词:注入那一刻就渲染成成品再落进历史。当时是什么就是什么,之后不会再变(注入是永久保留的)。
- system 各项:每次组装 system 时渲染,渲染完这一段标成不再插值,官方的插值器碰不到我们注入的花括号。
认不出来、这次拼错、或者取不到值的字段,{{名字}} 会原样保留,不报错、也不会让这次请求失败。名字不合法的(中文、大写)一律当普通文本。想真的写出 {{model}} 这几个字而不是让它被换掉,写成 {{ model }} 或者换个大小写就行。
模型作用域
- 模型键是
<provider>/<model>,比如deepseek-account/deepseek-flash。 - 弹层里「全部」和各模型同列,按 provider 分组,带搜索。
- 点「全部」= 对所有模型生效,含以后新增的,而且不会清空其它勾选;取消「全部」之后,之前勾的模型继续生效。
- 既没勾「全部」也没选模型,这条规则当前不生效,卡片上是
无应用(虚线圈出)外加一句轻提示。 - 存过的模型键在当前目录里找不到了,会标成
已失效并保持勾选,等你自己去取消;在你取消之前,它按不匹配处理,也就是不注入。 - 目录跟着 dsh 实时走:在 dsh 里加删改模型、改密钥,设置页马上就能看到,不用重启 dsh、也不用刷新页面。某个 provider 的目录没拉全时,列表顶部会说一句,那些没列出来的模型就会显示成「已失效」——这不是规则坏了。
- 「这一步算哪个模型」只有一套判据:优先官方模型选择投影(就是你在下拉里点的那个),拿不到再退到这次会话最近一次请求的路由,再退到 agent 建立时的选项。system 头部 / 尾部更准一层,直接用这次组装定下来的 provider 和模型。
配置存在哪里
~/.dsh/dsh-prompt-inject/rules.json(Windows 上一般是 C:\Users\<你>\.dsh\dsh-prompt-inject\rules.json,.dsh 文件夹路径由宿主决定)。
写入走原子替换,带跨进程文件锁,权限 0600。文件坏掉或者结构版本不认识时,它会带着时间戳改名成 rules.json.<时间戳>.bad 留着(现场可查),然后用空配置继续跑,不会静默把你的规则清空。这个文件由插件自己读写,建议只在设置页里改。
结构长这样,一条规则就是一个对象,八个注入点每个都只是一串规则,没有例外:
{
"version": 1,
"revision": 12,
"slots": {
"user.prefix": {
"rules": [
{
"id": "r1",
"enabled": true,
"text": "总是用中文回答",
"models": { "all": true, "list": ["deepseek-account/deepseek-flash"] }
}
]
},
"system.beforeTools": { "rules": [] }
}
}
revision 每次写入加一,用来挡掉两个窗口同时写的情况。
已知限制
- 用户前置词 / 后置词永久留在会话历史里,见上面那一节。
- 设置页不显示「当前模型」。多会话并行时那个值不可信,切换也未必及时反映,拿来提示只会误导。规则按
provider/model匹配这件事,看卡片上的作用域结论和弹层就够了。 - 没有「这条规则最终拼成什么」的预览区。目前暂不做。
- 排序只有 ↑ / ↓,没有拖拽。
- 模型目录的实时更新只覆盖 dsh 自己推送的变化。直接编辑 dsh 的配置文件、或者远端 provider 自己上下架模型,这边不会自动知道 —— 刷新页面就能拿到新目录。
- 有几道硬上限:一个注入点最多 200 条规则,单条正文最多 20000 字,一条规则最多勾 500 个模型键。超了保存会失败,并逐条告诉你哪一项有问题,已有的配置不会因此丢掉。
- 设置页文案目前以中文为主,注入点键名和模型键保持英文原样。
- 目标版本写死在
engines.dsh(现在是0.2.0-rc.2)。dsh 还在 developer preview,官方明确会有破坏兼容的改动,升级 dsh 之后如果发现这个插件加载不起来,多半就是要跟着适配。
开发
一个 npm 包,两半:
- host:cordis 插件
lib/index.js,管配置文件、注入逻辑、HTTP 端点。 - client:
lib/client.js,手写浏览器包,不走构建链,由package.json的dsh.client声明。
设计文档在 docs/,都挺短:
| 文件 | 讲什么 |
|---|---|
| docs/injectPoint.md | 注入点、官方段位、两种语义的由来与约束 |
| docs/injectFormat.md | 前后缀的注入格式、模板字段与渲染时机 |
| docs/injectScopeAndSetting.md | 规则模型、怎么算「这次是哪个模型」、配置文件位置 |
| docs/settingGUI.md | 设置界面、数据通道(三个 /api 端点)、目录刷新的事件表 |
动手前值得知道的几件事:
- 注入点表有两份:
lib/injectPoints.js是权威的那份,lib/client.js里内联了一份一模一样的(客户端半不能 import)。改表要两边一起改,.agents/checks/client-shape.mjs会检查两边是否一致。 @deepseek-ai/*一律声明成peerDependencies,不能装成本地依赖 —— 那样会变成两份模块实例,instanceof和模块私有的 WeakSet 都会失效。- 端点开在官方
/api下面,鉴权由官方那层处理,插件自己不写;所以只支持GET/HEAD/POST。 - 校验脚本分两组:
npm test跑纯 node 那组(clone 下来就能验,不依赖任何官方包);npm run test:host跑要官方包那组,没临时解包会直接退出码 2 并打印怎么搭(npm run test:all两类一起)。细节见 .agents/checks/README.md。 - host 侧代码改了要重启 dsh;只改客户端那一半刷新页面就行。
- 项目协作规范见 AGENTS.md。
No comments yet. Be the first to write one.