全部插件

DSH / BUNDLE / BUNDLES

@dsh-user/narrative-voice

v0.7.0TellToday / dsh-narrative-voicee30ca7e7cb

可安装组合包组合包与其他模块社区 · Topic 自动分析

概览

@dsh-user/narrative-voice

A DSH (DeepSeek Harness) bundle plugin that rewrites the ask_user_question tool description at prompt-assembly time with a fixed narrative-voice rule (default: the answerer narrates), toggled live via the /voice command.

README / ZH

插件文档

dsh-narrative-voice

English · 中文版

一个 DSH(DeepSeek Harness)bundle 插件:让 ask_user_question 工具生成的问题和选项人称始终一致。它在提示词组装阶段把该工具的 questions.description 整句改写为带固定叙述人称规则的描述,并可用 /voice 命令随时开关

为什么会有这个插件

ask_user_question 工具负责向人类问一个简洁的问题,并给出几个可点击的选项。没有固定规则时,模型会在同一个选项内部漂移人称

  • 选项标签用回答者口吻("我自己重启"——"我"=被问的人),选项描述却漂移到 AI 口吻("我会指导你如何重启"——"我"=AI);
  • 还会漏出"用户"这个词("…让你更省心"),把正在读这句话的人用第三人称指代。

同一个选项里出现两个不同的"我",用户根本分不清:提供动作的是谁?"我"到底是谁的视角?

本插件在提示词层面解决:把一条固定的人称规则写进工具自身的描述里,让模型生成的问题(question)、标题(header)、选项标签(label)、选项描述(description)始终用同一种视角,并且永远不称呼回答者为"用户"

它做什么

规则把工具的字段分成两组:

  • question(问题)和 header(标题)——两套方案都一样,固定从 AI 视角写:"我"/"I" = AI,"你"/"You" = 被问的人(用户)。
  • label(选项标签)和 description(选项描述)——跟着所选方案走:
    • 方案B(默认,voice: "user"——回答者叙述:"我"/"I" = 被问的人,"你"/"You" = AI。
    • 方案A(voice: "ai"——AI 叙述:"我"/"I" = AI,"你"/"You" = 被问的人。

为什么这样设计?为什么要把该工具的字段分成两组?

question(问题)和 header(标题)——这是 AI 在向用户提问。 提问的人当然用第一人称:"我(AI)想问你(用户)一个问题"。所以这两项固定用 AI 视角:"我" = AI,"你" = 用户,两套方案都一样,永远不变——否则问题里冒出指代用户的"我",读者会以为是用户在自己问自己。

label(选项标签)和 description(选项描述)——这是用户将要给出的答复。选项本质上是 AI 预先猜好、替用户写成的回复。但这句答复该用谁的口气说,存在两种合情合理的看法,也就是方案A/B的分野:

  • 方案B(回答者叙述):用户把选项当成自己亲口打出的回复——第一人称在用户手上,AI 只是把用户想打的字猜好、以选项的形式还给他。所以选项里:"我" = 用户("我重启…"),"你" = AI("你会指导我…")。

  • 方案A(AI 叙述):用户把选项当成 AI 递给自己的一道选择题——就像 AI 在说"我遇到个问题,给你两个选项,你选吧:A 我这样处理,B 我那样处理"。所以选项里:"我" = AI("我帮你处理…"),"你" = 用户。

规则只在 ask_user_question 这一个工具内生效(普通回复、其它工具完全不受影响),并且只在确实用到人称时才适用——不强迫本来用不到人称的问题或选项硬塞。

原理

DSH 每次发模型请求前执行 SystemPrompt.assemble(),把拼好的提示词放进一个 assembly 对象,派发到 system-prompt/assemble 这个 Cordis 瀑布事件——瀑布的返回值才是真正发给模型的提示词

本插件在瀑布上注册一个 global: true 的监听器:开启时,在 assembly.tools 里找到 ask_user_question,把 parameters.properties.questions.description(组装后的真实形态是 JSON-Schema)原地替换,再 return next() 放行。只改每请求的克隆——注册表 schema 和参数校验闭包都不受影响。

assemble() 每条消息执行一次,所以 /voice 开关下一条消息就生效,任何对话(包括已经开始的老对话)里都一样。

安装

一条命令,直接从本 GitHub 仓库安装(已实测可用):

dsh plugin --profile <profile> add "github:TellToday/dsh-narrative-voice#main"
  • #main 跟随最新提交;
  • 装完要重启该 profile 的进程(web profile 就是 dsh web)。

等效的其它写法:

# 完整 git URL
dsh plugin --profile <profile> add "git+https://github.com/TellToday/dsh-narrative-voice.git"
# 或用本地目录(开发用)
dsh plugin --profile <profile> add "C:\path\to\dsh-narrative-voice"

包声明了 dsh.bundle.patchdsh plugin add 会自动把它追加到 dsh.profile.bundles 成为一层 bundle。卸载:dsh plugin --profile <profile> remove @dsh-user/narrative-voice

前置:pnpm 在 PATH 上。git 托管安装会通过系统 git 克隆仓库(遵循你的 git 代理设置)。

用法

命令 作用
/voice on 开启改写(下一条消息起生效)
/voice off 关闭改写(工具描述恢复原样)
/voice user 切到方案B(回答者叙述)并开启
/voice ai 切到方案A(AI 叙述)并开启
/voice 查看当前状态(开关 + 当前方案)

命令由宿主的 commands 服务处理(不经过模型),即时生效——不依赖 HMR、无需重启。

默认配置

默认值 含义
voice user(方案B) 用哪种叙述方案
defaultActive true 装好后默认开启

想改默认值(而不是运行时用 /voice 切),在 profile 的 patch 文件里按 id 覆盖(路径 $DSH_HOME/profiles/<profile>/cordis.patch.yml)。patch 是整体替换 config,键要写全:

- id: narrative-voice
  config:
    voice: user          # user(方案B:回答者叙述)| ai(方案A:AI 叙述)
    defaultActive: true  # false = 默认关闭,直到 /voice on

配置由 Config 校验(零依赖的 Standard Schema 实现):非法值会让插件加载失败并报明确错误。

目录结构

dsh-narrative-voice/
├── lib/index.js          # 插件本体:Config、assemble 监听器、/voice 命令
├── cordis.patch.yml      # bundle patch:把插件行插入 host 平面
├── test/
│   ├── functional.mjs    # 隔离功能测试(39 项断言)
│   └── run-test.ps1      # 直接跑测试(无需安装、无需 junction)
├── package.json          # bundle 元数据(dsh.bundle.patch;零依赖)
├── LICENSE               # MIT
└── README.md / README.zh-CN.md

开发

pwsh ./test/run-test.ps1

插件没有任何裸 import,测试直接用 node 跑——不用装任何东西、不用清理任何东西。

License

MIT——见 LICENSE