Skip to content

dsh-round-inject

Verified

dsh-round-inject · v0.1.28 · MIT · Web UI

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

Install

dsh plugin add dsh-round-inject

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Tags

Creators

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 瀑布上执行注入:

  1. 计数与书签共用一个 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) 折叠,绝不每步全量扫描日志。
  2. 注入(副作用) —— 监听 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 publish

License

MIT