dsh-round-inject
v0.1.28
Published
Periodic prompt injection for the DeepSeek Harness Web GUI: every N model invocations (conversation turns and tool-call steps both count) the plugin injects a user-configured prompt as a model-visible user message; the first injection happens at conversat
Maintainers
Readme
dsh-round-inject
为 DeepSeek Harness Web GUI 提供的周期性提示词注入插件。
每 N 次模型调用(对话轮与工具调用轮各计一次)注入一次用户配置的提示词,作为模型可见、带来源标记的用户消息进入上下文;每次对话开始默认注入一次。
| | |
| --- | --- |
| Host | agent/pre-step 瀑布(统计每个真正进入的 step,并把注入消息追加到该 step 的请求) |
| Client | 一个设置页(settings.section),通过 configForms 读写本条目 Config。DSH 0.1.7 会依据 volatile Config 字段派生表单数据,但并未随附任何会为其渲染页面的客户端(autoGenerate 是给未来客户端预留的),因此仍需自行提供页面 |
| 配置 | 插件条目自身的 Config(live volatile() 字段),在自动生成的设置页中编辑 |
功能
- 轮次统计 —— 每次模型调用计 1 次,包括工具结果触发的下一步(工具调用后模型会再次被调用 = 又一个 step)。计数器按 agent 隔离(主会话与子代理互不影响)。
- 周期注入 —— 距上次注入恰好满
interval个已完成模型调用后,把配置的提示词追加到那一步的消息中(默认 50):开启开始注入时,注入发生在会话的第 1、1+interval、1+2·interval… 次调用;关闭时发生在第 interval、2·interval、3·interval… 次调用。注入消息携带source: { kind: 'plugin', plugin: 'round-inject' },写入会话日志,模型可见。 - 对话开始注入 —— 开启且填了开始提示词时,会话的第一次模型调用即携带开始提示词,周期计数随后从这次调用重新起算。
- 设置界面 —— 设置 → 提示词注入:启用开关、触发轮次(数字输入,默认 50)、注入提示词(多行输入框)、对话开始时注入开关。写入通过标准
settingsScope服务落到round-inject命名空间(持久化到 DSH 设置文档)。 - 默认安全 —— 提示词为空 ⇒ 不注入;关闭 ⇒ 不计数也不注入;不真正调用模型的 step 不会被计数。
安装
本包是发布到 npm 的 DSH 插件。在 DSH profile 目录执行:
dsh plugin --profile web add dsh-round-inject或手动把依赖加入 profile 的 package.json,并把 "dsh-round-inject" 追加到 dsh.profile.bundles,然后重启 profile。
配置
组合行(同时也是设置命名空间的 base 层):
- **0.1.17** —— 修复 0.1.15 适配引入的挂载失败:`cannot get property "sessionProjections" without inject`。Cordis 对服务访问有守卫,从 `ctx` 读取服务必须先声明 —— 插件现导出 `inject = ['sessionProjections']`(内置 fold 也是同样声明)。已在启用守卫的真实 cordis Context 下挂载验证通过。
- id: round-inject
name: 'dsh-round-inject'
config:
enabled: true # 总开关
interval: 50 # 两次注入之间的模型调用次数
prompt: '' # 注入的提示词文本(为空则不注入)
injectOnStart: true # 每次对话开始注入一次所有值都可以在 设置 → 提示词注入 中实时修改,无需重启。
工作原理
宿主在 sessionProjections 接缝上注册一个纯 fold,并在 agent/pre-step 瀑布上执行注入:
- 计数与书签共用一个 fold ——
round-inject投影是对会话事件流的纯折叠,只消费内置事件、从不追加自定义事件类型,因此恢复会话永远不会因本插件的词汇失败:- 每个
step/end(一次完成的模型调用,与内置 "N 轮 · M 步" 统计同一事件)推进totalSteps,并在会话注入过之后推进sinceInject(距上次注入消息已完成的调用数); - 一条由本插件追加的
user/message(以其source: { kind: 'plugin', plugin: 'round-inject' }识别)记录lastInjectSeq并把sinceInject归零。 注册表把状态检查点到<root>/session_projcache/,因此计数与书签都扛得住压缩、翻页、会话恢复与宿主重启。书签放在这份派生状态里(而不是 0.1.11 会跨会话泄漏的设置命名空间,也不是 0.1.13 中不存在的session.events—— 后者让每一步都崩溃),使间隔精确:每个事件 O(1) 折叠,绝不每步全量扫描日志。
- 每个
- 注入(副作用) —— 监听
agent/pre-step瀑布(每个拟议 step 一次)。在next()返回 loop 自身决策后:- 忽略
reject决策与空消息 step(这些不会调用模型); - 通过 live
volatile()引用读取最新配置; - 读取投影状态(
totalSteps/sinceInject/lastInjectSeq)并判定:- 本会话从未注入且开启开始注入 → 把开始提示词追加到第一次模型调用;
- 从未注入且无开始提示词 → 把周期提示词追加到会话第
interval次调用; - 否则当
sinceInject >= interval→ 把周期提示词追加到下一次模型调用 —— 正好在上次注入之后interval步;
- 整个判定有防护:任何意外错误只记 warning 并放行该步(不注入),插件永远无法拖垮一轮 agent 运行。
注入消息作为
createUserMessage({ content: [{ type: 'text', text: prompt }], source: { kind: 'plugin', plugin: 'round-inject' } })追加,成为该步持久化用户消息的一部分 —— 模型可见、会话日志可审计,并适用于任何模型路由。
- 忽略
KV cache 说明
注入会改变注入 step 的请求内容,因此从该请求起 provider KV/prefix cache 会失效一个回合的若干 step。默认 50 轮时影响可忽略;调小轮次会增加失效频率。
更新历史
- 0.1.28 —— 兼容 DSH 0.2.x。
peerDependencies把@deepseek-ai/dsh-llm卡在^0.1.1-rc.1 || ^0.1.7-rc.1、@deepseek-ai/dsh-session-projection卡在^0.1.7-rc.1。0.x版本的脱字符 永远够不到下一个次版本(^0.1.7-rc.1实际含义是<0.2.0),而 DSH 会把每个@deepseek-ai/dsh*的 peer 与当前运行时做带预发布的比对 —— 于是从 0.2.0-rc.2 起, profile 判定本插件不兼容并直接跳过加载(设置页与注入就这么消失了,不报错、不崩溃)。 现改为开放下界(>=0.1.1-rc.1、>=0.1.7-rc.1),与同系列的dsh-per-message-model用法一致。除此之外没有任何改动:本插件用到的每个接口在 0.2.0-rc.2 中都原样存在,发布前已对着运行中的运行时逐项核实。 - 0.1.27 —— 周期性注入从不按节奏触发。「距上次注入的步数」计数器只在已经注入过一次之后才开始累加(它以上一次注入为起点),所以在首次注入之前它恒为 0,首次周期注入只能靠另一条兜底判据触发 —— 而那条判据会漂移。实测:真实会话在
interval: 50、497 次模型调用的情况下,只注入了 1 次,且落在第 443 次调用。现在把起点改为步数(lastInjectStep,首次注入前为-1),从会话第一次调用起每个完成的步都推进计数,首次与后续注入共用同一套算术:关闭开始注入时落在第 50、100、150… 次;开启时落在第 1、51、101… 次。投影stateVersion升到 3;过期的缓存行会被忽略并按日志重算,已有会话的注入书签得以保留、不会重复注入。 - 0.1.26 —— 两项修复。(1) 注入会报错:
failed format v4 message requires a producer-owned source kind—— 注入消息带的是已废弃的{ kind: 'plugin', plugin: 'round-inject' }包装,而会话格式 v4 的准入检查会直接拒绝它。现改为扁平的生产者标识plugin:round-inject(与框架自身 V3 转换器对第三方插件生成的写法一致);同时书签 fold 仍识别旧包装,已有会话的计数不会断。(2) 恢复原有页面布局:同样的五行(启用开关、触发轮次、会话开始提示词、周期注入提示词、对话开始时注入开关)、同样的中文文案与提示、3/6 行文本框,以及"草稿 + 保存按钮"的行为。只有数据通道变了 —— 改绑ctx.configForms.get('round-inject'),其快照与原页面所依赖的 status/value/writable 形状一致(settingsScope已不存在)。 - 0.1.24 —— 修复 0.1.22 之后仍存在的「该插件当前未加载」提示。页面背后的投影漏掉了
available字段,而共享框架只要看到该字段为假就渲染未加载提示 —— 于是即使whileServed已命中、表单快照本身已是 ready,界面仍把已加载的插件报成缺失。现在available与其他 shell 字段一起进入投影,且客户端测试会断言SettingsFormShell的每个字段都已发布,防止再次遗漏。 - 0.1.22 —— 修复恢复页面后暴露的两个缺陷:侧栏标签显示成原始键
nav(字典里只写了description,t('nav')查不到就回退成键名),以及正文显示「该插件当前未加载」(SettingsFormModel在 Host 的 describe 镜像收录该条目之前一律报available: false,所以无条件注册会在插件已加载时也显示这句提示)。字典已补上标签键,页面改为通过configForms.whileServed(['round-inject'])注册,与官方所有配置页一致 —— 条目被服务时出现,且此时必定报为可用。 - 0.1.21 —— 恢复 0.1.7 下的配置页。0.1.15 适配时误以为「0.1.7 会依据 volatile Config 字段渲染页面」而删掉了客户端半端;事实并非如此 ——
dsh-settings文档明确写着autoGenerate只是给「依据 schema 生成页面的客户端」预留的标记,且「目前没有任何随附客户端会这样做」。页面已恢复:注册到settings.section与 Plugins 页的plugins.row.config槽(键dsh-round-inject#round-inject),通过ctx.configForms.get('round-inject')读写,并用共享的SettingsForm组件渲染。host 半端在可选的ctx.inject(['settings'], …)子上下文中声明configure({ auto: false }),因此在没有 Settings 的部署里插件照常加载。同时补上inject = ['sessionProjections'](Cordis 会拒绝未声明的服务读取)。 - 0.1.17 —— 修复 0.1.15 适配引入的挂载失败:
cannot get property "sessionProjections" without inject。Cordis 对服务访问有守卫,从ctx读取服务必须先声明 —— 插件现导出inject = ['sessionProjections'](内置 fold 也是同样声明)。已在启用守卫的真实 cordis Context 下挂载验证通过。 - 0.1.15 —— 适配 DSH 0.1.7-rc.1。0.1.7 已移除
settings.register()/settingsScope:插件改为把 Config 字段声明为.volatile()(以.get()读取的实时引用),配置改动即时生效、无需重载插件。此版本误删了client.js(当时以为 0.1.7 会依据这些字段自动渲染页面;实际不会),0.1.21 已恢复。投影注册改为直连ctx.sessionProjections.register,与内置 fold 一致。注入行为不变:第一次模型调用带开始提示词,此后恰好每隔interval个已完成步注入一次。 - 0.1.14 —— 修复 "session.events is not iterable"(每轮运行失败):书签扫描误用了
session.events(会话对象上不存在的属性,公共接口是session.snapshotEvents())。书签现移入投影状态(派生、每事件 O(1)、可持久化),判定全程有防护,内部错误不再拖垮整轮。注入时机精确:开启开始注入时,注入发生在会话第 1、1+interval、1+2·interval… 次调用;关闭时在第 interval、2·interval、3·interval… 次调用(不再 ±1 漂移)。默认触发轮次从 80 调整为 50。
故障排查
DSH 共享宿主包声明为 peerDependencies(0.1.6)
插件把全部 DSH 提供的运行时包(@deepseek-ai/cordis、@deepseek-ai/dsh-llm、@deepseek-ai/dsh-settings、@deepseek-ai/schemastery)声明为 peerDependencies,而非 dependencies。DSH 通过共享宿主包目录(~/.dsh/profiles/node_modules/@deepseek-ai/)以符号链接指向宿主精确版本,插件必须经宿主解析这些包,不能自装副本。
0.1.6 之前插件把 dsh-llm 放在 dependencies,导致 pnpm 把独立的 [email protected] 提升到 profile 根目录。Node 解析会优先命中这份副本而非宿主的共享符号链接,于是插件与宿主跑在不同的 dsh-llm 实例上(宿主为 0.1.1-rc.2)。两个版本的 createUserMessage 签名恰好一致所以能跑,但任何 API 漂移都会静默出错。判断方法:在 profile 下执行 require.resolve('@deepseek-ai/dsh-llm/package.json'),若解析到 profiles/web/node_modules/... 而非宿主安装路径即为被遮蔽。更新到 0.1.6 后在 profile 里重装以清理旧副本:
cd ~/.dsh/profiles/web
pnpm add "[email protected]" # 清理旧的提升副本
node -p "require.resolve('@deepseek-ai/dsh-llm/package.json')" # 应指向宿主路径,而非 profiles/web/node_modules"设置服务不可用" / 设置项不可编辑 / 命名空间缺失
开发
├── index.js # Host 半端(ESM;设置注册 + session-start 重置 + pre-step 计数/注入)
├── client.js # 浏览器半端(设置页面;__ModuleLoader__.load 工厂,无构建步骤)
├── cordis.patch.yml
└── package.json无需构建 —— 两个半端均为手写 ESM / 浏览器工厂(与 dsh-strata 同模式)。发布:
npm publishLicense
MIT
