dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
A general-purpose prompt optimizer plugin for DeepSeek Harness: turn a casual composer draft into a clear, structured, executable prompt — reviewed by the user, written back to the draft, and never sent automatically.
把随手写的一句话,变成 Agent 真正能执行的任务定义。
它是 DeepSeek Harness 的通用 Prompt 优化器,插在「用户输入」和「主 Agent 执行」之间:
用户输入 → Prompt Optimizer → 用户检查 → Composer Draft → 用户自己按发送 → 主 Agent
在 Composer 右下角点一下「✨ 优化提示词」,插件读取当前草稿、识别任务领域、按强度补全结构与约束, 把结果放进一个预览弹窗(含并排对比与逐行 Diff)。只有你点「采用优化结果」时,文字才会写回输入框; 插件永远不会替你发送消息。
人对 Agent 说话时天然省略信息。帮我做个好看的管理后台 这句话里,没有信息层级、没有主要 CTA、
没有响应式要求、没有验收标准——Agent 只能猜,猜错就返工。
这个插件不做「润色」,也不做「把一句话变长」。它做的是:
理解 → 结构化 → 补全 → 约束 → 明确验收标准。
同时严格守住一条线:增强意图,绝不改变意图。
它不会因为你写了一句「做个数据库」就替你决定用 PostgreSQL,也不会因为你说「好看」就往提示词里 堆玻璃拟态、渐变和光效。它只把你说了但没写全的补齐,把含糊的说清楚,把该有的约束列出来。
0.1.7-rc.2(@deepseek-ai/dsh)web 或 desktop)——客户端按钮依赖 web 的 client module 系统dsh plugin 会把参数转发给 pnpm)# 1) 装进 profile(本地路径必须是绝对路径)
dsh plugin --profile web add "D:/code/dsh插件/dsh-prompt-optimizer"
# 2) 把它选进 bundle 层
# 注意:dsh plugin add 只跑 pnpm,不会自动写 dsh.profile.bundles
然后编辑 ~/.dsh/profiles/web/package.json,把包名加进 dsh.profile.bundles:
{
"dsh": {
"profile": {
"bundles": [
"...",
"dsh-prompt-optimizer"
]
}
}
}
重启 dsh --profile web 即可。
桌面版(Electron):
desktopprofile 由应用自己管理,CLI 会拒绝 (error: profile "desktop" is managed exclusively by the Electron application)。 在应用的 Plugin Hub 里安装本目录,或手工编辑~/.dsh/profiles/desktop/package.json的dependencies与dsh.profile.bundles, 然后重启应用。
dsh plugin --profile web add "git+https://github.com/YMRwithNoworry/dsh-prompt-optimizer.git"
同样需要手工把 dsh-prompt-optimizer 加进 dsh.profile.bundles。
重启后打开设置面板,左侧应出现「提示词优化器」一节;Composer 右下角(模型选择器左边)应出现 「✨ 优化提示词」按钮。宿主侧还会在启动日志里打一行:
dsh-prompt-optimizer: active — model current, intensity balanced, language auto, domain detection on
帮我做个好看的管理后台如果你在优化期间改了输入框,弹窗会提示「输入内容在优化期间发生了变化」,并额外给出 保留当前内容 与 应用优化结果 两个选项——它不会悄悄覆盖你刚打的字。
这是正确行为,不是失败:
你好 / 谢谢 / 好的 / 继续 —— 直接原样返回弹窗会显示「原提示词已经足够清晰,已按原样返回」以及「已跳过模型调用」。
设置面板里可以改,也可以直接编辑 ~/.dsh/prompt-optimizer.json。
| 设置 | 默认 | 说明 |
|---|---|---|
| 优化模型 | current | 下拉框,直接列出 DSH 已配置的 provider / model;current = 复用当前会话的模型 |
| 思考强度 | 跟随模型默认 | 选中模型支持的推理档位(如 medium / high / max);选「默认」则由该模型自己决定 |
| 优化强度 | balanced | light 只修表达 / balanced 补全约束并结构化 / deep 建立目标、标准与验收条件 |
| 优化语言 | auto | auto 跟随原文 / chinese / english / original |
| 自动识别领域 | 开 | 关闭后一律使用通用策略 |
| 优化后先预览 | 开 | 关闭后点击按钮直接写入输入框 |
| 参考会话上下文 | 开 | 仅用于消解「那个东西」之类的指代,不复制整段历史 |
| 参考项目规则 | 开 | 读取工作区规则文件(见下),不读源码 |
| Minecraft 专项优化 | 开 | 启用 Minecraft 领域策略 |
| 视觉上下文 | 开 | 草稿附带图片时保留引用,不凭空描述图片内容 |
| 自定义优化规则 | 空 | 例如「所有 UI 设计都优先考虑简洁和高信息密度」 |
点击按钮
↓ 读取 useInput().draft(不发送)
↓ POST /prompt-optimizer/optimize
↓
纯函数规划(无网络、无模型)
· detectDomain 领域打分
· analyzeComplexity 复杂度 + 字符预算
· shouldNoop 是否原样返回
↓ no-op → 直接返回原文,不调模型
↓
组装 Prompt Architect System Prompt
· 身份与十原则
· 领域检查清单(只列该领域专家会问的问题)
· 强度策略
· 复杂度与硬性字符上限
· 语言规则
· 可选上下文块(项目规则 / 用户规则 / 会话 / 附件)
· 输出契约
↓ ctx.llm.stream()(复用 DSH 的 provider / 认证 / 重试)
↓ 清洗模型输出(去掉前言与代码围栏)
↓ 返回 { optimized, original, plan }
↓
预览弹窗(并排 / Diff)
↓ 用户点「采用优化结果」
↓ inputActions.setDraft(optimized) ← 插件唯一的写入
↓
用户自己按发送
dsh-prompt-optimizer/
├── package.json dsh.bundle.patch + dsh.client 声明
├── cordis.patch.yml profile bundle patch:插入宿主插件行
├── lib/
│ └── client.js 浏览器半:手写 __ModuleLoader__ bundle,无需构建
├── src/
│ ├── index.ts 宿主半入口:挂路由、管设置
│ ├── route.ts HTTP 接口(/settings、/optimize、/health)
│ ├── settings.ts 默认值、归一化、原子写盘、model 路由解析
│ ├── config.ts schemastery 配置 schema(全部 volatile)
│ ├── optimizer/
│ │ ├── optimizer.ts 规划 → 调用 → 清洗
│ │ ├── system-prompt.ts Prompt Architect System Prompt
│ │ ├── domain-detector.ts 领域打分
│ │ ├── complexity.ts 复杂度与字符预算
│ │ ├── provider.ts 与 DSH LLM 服务的桥接
│ │ ├── assembler.ts 零依赖的流式文本装配
│ │ └── domains/index.ts 领域策略注册表
│ ├── context/
│ │ ├── project.ts 项目规则文件白名单读取
│ │ └── conversation.ts 会话上下文裁剪
│ └── shared/types.ts 两端共享的类型
└── test/ 187 个测试
领域不是关键词表,而是检查清单:该领域专家动手前会问的问题,以及该领域最容易犯的错。 系统提示词明确要求模型丢掉用户意图不支持的那些问题——这是「增强意图」不滑向「发明任务」的关键。
内置策略(src/optimizer/domains/index.ts):
| 领域 | 典型触发 | 它会补什么 |
|---|---|---|
minecraft | Minecraft / NeoForge / 材质 / 模组 / SRP | 原版视觉语言、像素网格、游戏内可读性、Mod 实现约束 |
ui-design | 界面 / 首页 / 后台 / 设计系统 | 信息层级、主要 CTA、交互状态、响应式、可访问性 |
image-generation | 生图 / 插画 / 概念图 / 立绘 | 构图、视觉焦点、风格锚点、色彩与明度结构、比例 |
game-development | 玩法 / 战斗 / 数值 / 关卡 | 核心循环、输入→反馈→奖励、数值交互、边界情况 |
coding | 代码 / bug / 重构 / 架构 / 数据库 / CI | 可观察行为、既有约定、影响面、错误处理、验证方式 |
writing | 写作 / 文案 / 文档 / 翻译 / 总结 | 受众、目的、语气锚点、结构、篇幅与平台 |
research | 调研 / 分析 / 对比 / 竞品 | 要支撑的决策、对比维度、信息源与时效、不确定性标注 |
data-analysis | 数据 / 指标 / 报表 / 统计 | 要回答的问题、输入形态、指标定义、缺失值处理 |
education | 讲解 / 教程 / 习题 | 学习者起点、具体产出、顺序、可验证的练习 |
business | 产品 / 项目 / 运营 / 营销 | 业务结果、成功指标、约束、干系人、风险 |
general | 其它一切 | 通用三问 |
只需在 DOMAINS 数组里加一个对象,检测器和提示词构建器会自动使用它:
{
id: 'video-production',
label: 'Video Production',
when: '剪辑、分镜、调色、字幕、导出。',
signals: ['剪辑', '分镜', 'premiere', 'davinci', '调色', 'timeline'],
considerations: [
'成片时长与投放平台。',
'叙事结构:开场钩子、信息密度、收尾。',
'素材规格与帧率,以及输出编码要求。',
],
sections: ['Design Direction', 'Output'],
avoid: ['只堆形容词而不给节奏与镜头决策。'],
}
不需要改动任何其它文件。
插件从会话工作目录读取一个固定白名单:
AGENTS.md · PROMPT_RULES.md · .prompt-rules · DESIGN.md · CLAUDE.md · CONTRIBUTING.md · README.md
这让项目可以拥有自己的 Prompt 风格、设计语言与输出规则:
全局规则(AGENTS.md / 设置里的自定义规则)
↓
项目规则(.prompt-rules / DESIGN.md)
↓
领域规则(Domain Strategy)
↓
当前任务
↓
最终 Prompt
只保留最近 6 轮、单轮最多 1200 字符、合计最多 4000 字符,并且只用于消解指代。 系统提示词明确禁止把历史复述进输出。
例如你前面说过「我要做一个恐怖生存 Mod」,后面写「给这个怪物加个技能」,优化器知道「这个怪物」 指前面讨论的生物,不会要求你重复一遍。
草稿附带图片时,插件把文件名传给优化器,并明确要求它:
以「附件的 」引用它们,不要描述或猜测其内容。
插件自身不读取图片内容,也不做任何视觉推断——那需要 DSH 已安装的视觉插件。
插件不硬编码任何模型,也不要求你重新配 API Key。
设置面板里的「优化模型」是一个下拉框,选项来自 DSH 自己的 LLM 服务:插件调用
ctx.llm.listProviders() 与 listModels() 枚举当前 profile 真正连得上的路由,再对每个路由调用
resolveModelInfo() 取它支持的思考强度。结果是:
provider/model;路由解析顺序:
provider/modelctx.agentDefaultModel)解析结果交给 ctx.llm.stream(),所以认证、重试、代理、适配器行为完全复用 DSH 现有的那套。
思考强度只在非空时才传给适配器。多数 provider 不暴露任何档位,传一个空字符串会让它们直接拒绝请求, 所以「默认」是真的不传。
温度固定为 0.2:架构师要的是可复现,不是创造力。
设置面板会说明原因,而不是只给一个空列表:
discovery.llmAvailable | providerCount | 含义 |
|---|---|---|
false | — | 该 profile 没有挂载 LLM 服务(llm / llm-pi-ai / llm-deepseek) |
true | 0 | 有服务,但没有任何 adapter 注册路由 |
true | >0 | 正常;failedProviders 里的 provider 表示它的 listModels 抛错或超时 |
无论哪种情况 current 始终可选,插件其余功能不受影响。
实现注意:cordis 只会把服务交给在
inject里声明过它的 fiber。直接读ctx.llm拿到的是undefined——不报错,只是所有 LLM 功能静默失效。又因为本插件必须在没有模型的 profile 里也能启动, 它不能把llm写进inject(那会变成必需依赖,插件会永远 pending)。 所以三个可选服务统一通过ctx.inject([name], scoped => …)惰性解析,见resolveServices。test/plugin-face.test.ts会断言源码里不存在任何ctx.llm/ctx.webServer直接读取。
宿主半在 profile 的 web server 上注册一个 prefix 路由 /prompt-optimizer:
| 方法 | 路径 | 说明 |
|---|---|---|
GET | /prompt-optimizer/settings | 当前设置、默认值、可发现的 provider/model 路由 |
POST | /prompt-optimizer/settings | 保存设置(归一化后原子写盘) |
POST | /prompt-optimizer/optimize | 执行一次优化 |
GET | /prompt-optimizer/health | 存活探测 |
所有 POST 都做同源检查:
Origin → 允许(桌面 shell 转发时会剥掉它)Origin 是 dsh-app:// → 允许(桌面页面)Origin 必须与 Host 一致,否则 403失败一律返回 HTTP 200 + 稳定的错误码(EMPTY_DRAFT、NO_ROUTE、AUTH、RATE_LIMIT、
EMPTY_OUTPUT、TRUNCATED、ABORTED、PROVIDER_ERROR、ROUTE_ERROR…),
客户端把它们映射成人话。任何失败都不会清空你的原始文本。
npm install # 只装 typescript 与 @types/node(开发期依赖)
npm run build # tsc 编译 src/ -> dist/
npm test # 先 build,再跑 201 个测试
宿主半是 TypeScript,但 Node 拒绝为 node_modules 下的文件做类型擦除:
Error: Stripping types is currently unsupported for files under node_modules
所以插件被 profile 安装后不能直接跑 .ts,必须提供编译产物。scripts/build.mjs 做两件事:
tsc 把 src/**/*.ts 编译到 dist/,并把显式的 .ts 导入说明符改写成 .jssrc/config.js(唯一一个不带类型标注的模块,见其文件头注释),并把它的
./settings.ts 改写成 ./settings.js构建结束前会扫描 dist/,只要还有一个 .ts 说明符就报错退出,避免把必然运行失败的产物发出去。
浏览器半不需要构建:它是手写的 window.__ModuleLoader__.load({ id, factory }) bundle,
只 require("react")(platform seed 表里的模块)。
dist/ 是提交进仓库的,这样 git 安装无需构建步骤。改完源码记得 npm run build——
test/packaging.test.ts 会检查 dist/ 是否比源码旧。
pnpm installpnpm 换目录是「先删旧的、再改名新的」。如果目标目录正被占用——DSH 桌面应用运行时就会占用 它加载的插件目录——这次换目录会中途失败,被删掉的包可能只留下一个空壳。
本插件在一次开发中就这样损坏了 desktop profile 的 dsh-plugin-product-design(它的
skills/ 目录被应用持有)。pnpm install 无法自愈,因为它会撞上同一把锁并报
failed to remove existing directory ... prior to swap。
更新已安装的插件不需要 pnpm。 用这个脚本按文件覆盖,它没有目录换名,因此应用运行时也能用, 也不会把包删成空壳:
npm run sync -- desktop # 把当前构建同步进 desktop profile
npm run sync -- web --check # 只看会写哪些文件
首次安装仍然需要 pnpm(或应用自己的插件管理器)来登记依赖;之后更新用 sync 就够了。
正确做法:安装插件前先完全退出 DSH。若已经损坏:
# 1. 完全退出 DSH 桌面应用
# 2. 让挂起的删除完成,然后修复
npm run repair:product-design -- desktop
# 或者用 pnpm 重装(仅在没有 DSH 运行时)
cd ~/.dsh/profiles/desktop && pnpm install
repair:product-design 会检查包是否完整;若 skills/ 缺失就就地恢复,并在目录仍被占用时
明确告诉你先退出应用,而不是静默失败。pnpm 在换目录失败时会把被移除的包留在
node_modules/.ignored/,所以数据通常是可以找回的。
profile 用 file: 安装时 pnpm 默认是拷贝而不是软链,所以改完代码要重新装一次:
cd ~/.dsh/profiles/desktop && pnpm install
想免去这一步就用 link::
dsh plugin --profile web add "link:D:/code/dsh插件/dsh-prompt-optimizer"
| 能力 | 用到的 API |
|---|---|
| 按钮位置 | ctx.slots.inject("conversation.input.right") + ctx.slots.register() |
| 预览弹窗 | ctx.slots.inject("conversation.input.overlay") |
| 设置页面 | ctx.slots.inject("settings.section") |
| 读取草稿 | slot 的标准 props:useInput(s => s.draft) |
| 写回草稿 | inputActions.setDraft(text) |
| 模型调用 | ctx.llm.stream() |
| 默认模型 | ctx.agentDefaultModel.currentSelection() |
| HTTP 路由 | ctx.inject(["webServer"], …) + webServer.register({ kind, path, handler }) |
| 配置表单 | Config schema(全部字段 .volatile()) |
没有使用 querySelector 注入按钮、MutationObserver、DOM 结构硬编码、fetch/XHR 劫持、
webpack 修改或任何 DSH 源码补丁。测试会断言这些模式不出现在客户端 bundle 里。
cd dsh-prompt-optimizer
npm test # 187 个测试
npm run test:verbose # 逐条列出
覆盖范围:
| 文件 | 覆盖 |
|---|---|
domain-detector.test.ts | 领域识别、优先级、minecraftOptimization 开关、注册表不变量 |
complexity.test.ts | 问候语→trivial、一句话→simple、多部分→complex、字符预算 |
optimizer.test.ts | 规划纯度、no-op 判定、路由解析(显式/会话/默认/无)、供应商失败、空输出、取消、输出清洗 |
system-prompt.test.ts | 十原则、禁止新增功能与事实、禁止填充词、强度策略、语言规则、上下文块、草稿 JSON 转义防注入 |
settings.test.ts | 默认值、逐字段降级、原子写、损坏文件、路由解析边界 |
config.test.ts | schema 构建、默认值一致、每个字段都是 volatile、字段集合完整 |
context.test.ts | 规则文件白名单(源码不可读)、体积上限、会话裁剪 |
route.test.ts | 设置读写、优化成功/失败、空草稿与 no-op 不调模型、同源检查、超大 body |
client.test.ts | 按钮注册与三个 slot、空输入禁用、点击永不 submit、采用写入、竞态保护、全部失败路径 |
client-render.test.ts | 渲染结果本身:计划标签、Diff 切换与增删摘要、错误文案、no-op 说明、模型与思考强度下拉框的全部行为 |
model-routes.test.ts | 模型发现:provider/model 枚举、强度元数据、超时与抛错的降级、路由上限、取消 |
copy.test.ts | 文案表无重复键、静态引用的文案键都已定义 |
plugin-face.test.ts | inject 形状(复刻 cordis 的 Inject.resolve)、禁止直接读 ctx.llm 等可选服务、两端导出的插件面 |
packaging.test.ts | 相对导入必须有扩展名、dist/ 存在且无残留 .ts 说明符、bundle patch 与 exports 一致 |
其中几条是回归测试,对应开发中真实出现过的 bug:
inject 声明形状错误:{ optional: [...] } 被 cordis 当成一个名为 optional 的必需服务,
插件会永远 pending、静默不加载。现在 plugin-face.test.ts 复刻了 Inject.resolve 来钉住语义。ctx.llm 拿到 undefined:cordis 只把服务交给声明过它的 fiber。这个 bug 让模型发现
返回 0 条路由、/optimize 必然 NO_ROUTE,而日志里没有任何线索。现在服务统一走
ctx.inject([name], scoped => …) 惰性解析,并有源码级断言禁止直接读取。node_modules 下做类型擦除:改为编译到 dist/,构建末尾会扫描产物,
只要还剩一个 .ts 说明符就报错退出。optimize() 没有解析设置里的 provider/model,显式路由被忽略TEXT.original 被语言选项的同名键覆盖,导致 Diff 栏标题显示成「原文语言」client-render.test.ts 里的 React 替身最初把 children 放在元素节点上(node.children),
而 React 实际放在 props.children。后果是 Pane 里 props.children !== undefined 恒为 false,
Diff 视图的子内容被静默丢弃——测试却"通过"了,因为它只检查了 diff 开关的状态。
替身现在与 React 一致:children 进 props.children,展开后为空则删除该属性。
教训写在这里,是因为这类替身偏差不会报错,只会让测试失去意义。
submit()、没有 agent.followup、没有 agent.steer。
唯一的写入是 inputActions.setDraft()。测试对源码做字符串断言来守住这条线。<draft> 标签;即使草稿里出现
</draft>,它也只是字符串内容,无法闭合真正的定界符(有测试覆盖)。settings.yaml。 插件只写自己的 ~/.dsh/prompt-optimizer.json,用临时文件 + rename 原子写入。| 现象 | 原因 / 处理 |
|---|---|
| 找不到按钮 | profile 没有 client module 系统(只有 web 系 profile 有);或包名没加进 dsh.profile.bundles |
| 按钮是灰的 | 输入框为空,或正在提交中,或 composer 被锁定 |
找不到可用的模型路由 | 设置里填了 current 但当前会话没有选中模型;改成显式 provider/model |
模型认证失败 | DSH 的凭据配置问题,与本插件无关 |
启动日志 skipping profile bundle "dsh-prompt-optimizer" | bundle 解析失败——检查 package.json 的 dsh.bundle.patch 与文件是否存在 |
declares dsh.client but exports no "./client" bundle | exports["./client"] 缺失或指向不存在的文件 |
require("…") missed the module table | 客户端 require 了 platform seed 表之外的模块;本插件只 require("react") |
| 设置页没有「提示词优化器」 | 客户端 bundle 没加载;看浏览器控制台 |
| 优化结果被包在代码围栏里 | 已被 normalizeModelOutput 处理;若仍出现请提 issue |
针对 DSH 0.1.7-rc.2 开发并验证。用到的都是这一版本真实存在的公开扩展点:
conversation.input.right / conversation.input.overlay / settings.section 槽InputZone({ session, input })与 InputActions.setDraftwebServer.register({ kind: 'exact' | 'prefix', path, handler })ctx.llm.stream()、ctx.agentDefaultModel.currentSelection()Config schema 的 .volatile() 语义window.__ModuleLoader__.load({ id, factory }) 客户端模块契约若未来 DSH 改变其中任一项,需要同步修改的地方在「它用到的 DSH 扩展点」表里已逐项列出。
MIT