dsh-prompt-polish
dsh Web 插件:在输入框模型名左侧加一个小星星按钮,一键把当前草稿交给模型改写成结构化提示词,并可一键撤回
xyavid
@xyavid
⬇ 1
★ 2
main
安装
dsh plugin --profile web add github:xyavid/dsh-prompt-polish
需要可复现安装时,可在仓库后追加 #commit 固定提交。
dsh Web 插件:在输入框模型名左侧加一个小星星按钮,一键把当前草稿交给模型改写成结构化提示词,并可一键撤回
该插件未提供要点说明,请参考仓库 README。
- 安装并启动 DeepSeek Harness:
npx @deepseek-ai/dsh web - 在终端执行上面的安装命令(CLI 会解析插件并核验来源)
- 用 dsh plugins list 确认已安装,必要时重启 Harness 生效
插件以当前 dsh 进程的权限运行,安装时可能执行代码。请先通读仓库源码与许可证,确认无破坏性命令与越权访问;本站只做索引,不对第三方插件安全性作担保。
| 代码仓库 | github.com/xyavid/dsh-prompt-polish |
| 许可证 | MIT |
| 主要语言 | main |
| 下载量 | 1 |
| GitHub 星标 | 2 |
| 最近推送 | 2026-09-13 |
| 收录日期 | 2026-09-19 |
| 分类 | 界面增强 |
事实信息来自公开插件目录快照(2026-10-01),介绍文案由本站再加工。
以下为插件仓库 README 全文(原始内容,由公开目录抓取整理)。
# dsh-prompt-polish
[](https://github.com/xyavid/dsh-prompt-polish/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/@xyavid/dsh-prompt-polish)
[](./LICENSE)
[](https://github.com/deepseek-ai/deepseek-harness)
**Prompt polishing for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) web GUI** — one click turns a rough composer draft into a clearer, more actionable prompt: explicit goal, concrete requirements, constraints, and acceptance criteria. The button sits right next to the model name, calls the model your session already uses, writes the result straight back into the composer, and then turns into an undo arrow.
English | [简体中文](./README.zh-CN.md)
---
## Why
A prompt that a coding agent can act on usually states *what to build*, *where*, *under what constraints*, and *how success is judged*. Most drafts don't — they are quick notes. This plugin adds a **polish affordance** to the dsh composer: it sends the draft through a low-temperature rewrite pass over the harness's own LLM service, fills the improved text back, and keeps the original one click away.
Nothing about your intent changes: the strategy is allowed to make the ask clearer, never to invent requirements. Your API key never reaches the browser, and no part of the flow touches the session's model history.
## Features
| | |
|---|---|
| ✨ **Composer button** | A small sparkle button in the composer tool row, immediately left of the model name — always at hand, and it reflows with the row |
| 🔁 **Three states, one button** | Idle (sparkle) → click to polish. Running (spinner) → click to cancel. Done (undo arrow) → click to restore the original draft. No extra panels, bars, or dialogs |
| 🧠 **Your session's model** | The call goes through `ctx.llm` on the route your session already uses, falling back to the harness-wide default model (`agent-default-model`) — no second model to configure, no separate key |
| 🔑 **Zero credential setup** | Calls ride the harness LLM service; keys come from the harness credential store and are used host-side only |
| 🛡️ **Draft safety** | Empty, attachment-only, over-length, and command/reference-chip drafts are refused up front; a failed call never modifies what you typed |
| ↩️ **Two kinds of undo** | Native `Ctrl+Z` (the write goes through the official editor API) and the button's own undo state |
| ⚙️ **Live settings** | `enabled`, strength, temperature, token and length budgets, timeout, and the strategy prompt apply from Settings → 插件配置 with no restart |
| 🌏 **Language follows the draft** | Chinese in → Chinese out, English in → English out; code, paths, identifiers, and error text are preserved verbatim |
| 🚫 **No fabrication** | Expression-level rewriting only: no new requirements, no invented facts, no technology choices you did not imply |
| 🩺 **Runtime diagnostics** | Client state is exposed on `window.__dshPromptPolish` (`stage` / `attempts` / `mounted` / `errors`) for one-command troubleshooting |
> **UI copy is Chinese-only in this version** (button tooltips such as `优化提示词` / `取消优化` / `撤回优化`,
> and the local-guard hints). Hooking into the harness locale namespace for zh/en dictionaries is on the
> roadmap; the settings section itself is described in Chinese because it renders the schema descriptions.
> Screenshots are in [Feature showcase](#feature-showcase) below; `docs/compatibility.md` records what
> was field-verified on which build.
## Feature showcase
**The button, in place.** The sparkle button lives in the composer tool row, immediately left of the model name, so polishing never takes you away from the draft you are writing.

**Before.** A quick note rather than a task brief: the goal, the constraints, and the acceptance criteria are all left implicit.

**After.** One click rewrites it into a structured, actionable prompt and writes the result straight back into the composer; the button then turns into the undo arrow.

## How it works
```mermaid
flowchart LR
A[Composer draft] --> B{Local guards
empty / attachments / chips / phase}
B -- reject --> R[Button turns red:
reason shown on hover, draft untouched]
B -- pass --> C["POST /api/prompt-polish/optimize
(same-origin, cookie-authenticated)"]
C --> D["ctx.llm.stream
low-temperature rewrite"]
D --> E[Validate finish:
error / aborted / truncated → refuse]
E --> F[Normalize:
strip fences and wrapping quotes]
F --> G["inputActions.setDraft(result)
native Ctrl+Z can still undo"]
G --> H[Button becomes the undo arrow]
H -- click --> I[Original draft restored]
```
The plugin is **one npm package with two halves**, following the dsh plugin conventions:
- **Host half** (`exports "."`, Node): registers the `prompt-polish` settings namespace (schemastery — rendered automatically by the built-in plugin config page) and the `POST /api/prompt-polish/optimize` route on the shared webserver. The model call uses `ctx.llm.stream` with the auxiliary-call discipline the harness applies to session titles: composed deadline plus caller cancellation rechecked during and after the stream, terminal-finish validation, and refusal of truncated output.
- **Browser half** (`exports "./client"`, loaded through `dsh.client`): registers the button into the `conversation.input.right` slot, reads the draft with `useInput`, writes it with `inputActions.setDraft`, renders the three button states, and mirrors `undoWindowMs` through `ctx.settingsScope`. It consumes exactly two services — `slots` and (optionally) `settingsScope` — and never renders custom inject-face hooks; see [docs/compatibility.md](./docs/compatibility.md) for why that distinction matters. `dsh.client.inject` is intentionally empty: it lists *client-package* dependencies for graph ordering, and this bundle has none.
The two halves never share code at runtime: the browser half receives only the wire types and the prompt normalization helper, and the host half does all credential-bearing work.
## Requirements
| | |
|---|---|
| dsh | `>= 0.1.5-rc.1` (verified on `0.1.5-rc.1`) |
| Profile | `web` — the desktop app and `dsh web` share this profile |
| Node | `^22.19.0 \|\| >=24.0.0` (only for building from source) |
| Plugin | `0.2.x` |
The host half declares `inject: ['llm', 'settings']`; `webServer`, `sessions`, and the default-model namespace are probed optionally and degrade gracefully when absent.
## Install
**From npm (recommended).** The published tarball already contains the built `lib/`, and registry
installs run no lifecycle scripts:
```bash
dsh plugin --profile web add @xyavid/dsh-prompt-polish
```
**From a local checkout.** The built `lib/` is committed, so a checkout is usable as-is:
```bash
git clone https://github.com/xyavid/dsh-prompt-polish.git
cd dsh-prompt-polish
dsh plugin --profile web add .
```
To develop against a live checkout, build first and link the directory instead:
```bash
pnpm install
pnpm run build
dsh plugin --profile web add link:C:\path\to\dsh-prompt-polish
```
**From a tarball.** No build environment needed:
```bash
pnpm pack # produces xyavid-dsh-prompt-polish-0.2.0.tgz
dsh plugin --profile web add ./xyavid-dsh-prompt-polish-0.2.0.tgz
```
**From GitHub.** The package declares a `prepare` script, and pnpm >= 10 blocks dependency build
scripts by default, so a git install has to be allowlisted first; without it pnpm aborts with
`ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED`. Add this to the profile's `pnpm-workspace.yaml`
(`$DSH_HOME/profiles/web/`):
```yaml
onlyBuiltDependencies:
- "@xyavid/dsh-prompt-polish"
```
```bash
dsh plugin --profile web add github:xyavid/dsh-prompt-polish
# or pin a commit for reproducibility:
dsh plugin --profile web add github:xyavid/dsh-prompt-polish#
```
**Uninstall:**
```bash
dsh plugin --profile web remove @xyavid/dsh-prompt-polish
# then restart dsh web
```
`dsh plugin add` appends the bundle to `dsh.profile.bundles` automatically (the package declares `dsh.bundle.patch`); `remove` takes it back out. After either operation the running profile keeps its startup bundle set, so **restart** before expecting a change.
## Usage
Type a draft, then click the sparkle button left of the model name.
**The three states**
| State | Icon | Click behaviour | Hover text |
|---|---|---|---|
| Idle | ✦ sparkle | start polishing | `优化提示词` |
| Running | ↻ spinner (hover turns red) | cancel the in-flight call | `取消优化` |
| Done | ↺ undo arrow (accent colour) | restore the original draft | `撤回优化` |
**What happens, step by step**
1. **Local guards run first.** Empty or whitespace-only drafts, attachment-only drafts (a chip's trailing zero-width space is folded away correctly), drafts over `maxInputChars`, drafts containing `/commands` or `@references`, and a composer in a non-`plain` phase are refused with a tooltip. Chips are refused because filling text back would destroy them.
2. **The button spins while the host calls the model.** Your draft is untouched during the call, so cancelling at any moment leaves exactly what you typed.
3. **On success the result is written back** through the official editor API, the native undo stack stays intact (`Ctrl+Z` works), and the same button becomes the undo arrow.
4. **Undo restores the original draft** and returns the button to idle. If you keep typing after the result lands, the undo state retires itself so stale text can never overwrite your newer edits.
**Interaction rules worth knowing**
- Pressing send while a polish is running cancels the call and sends your original text.
- Editing the draft while a polish is running means the result is **not** written back; the button returns to idle and the hover text explains why.
- The undo affordance expires after `undoWindowMs` (default 60 s). The button reads that value through the client settings mirror, and falls back to 60 s when the mirror is unavailable (see Known limitations).
- Only one polish runs at a time per session, and a late response from a cancelled call is discarded.
## Configuration
Everything lives in the `prompt-polish` settings namespace, editable from **Settings → 插件配置** or directly in `~/.dsh/settings.yaml`. Changes are applied live (`applies: 'live'`).
Defaults come from two places, in this order: the schema defaults below, overridden by the composition layer's `config` block in `cordis.patch.yml`, overridden by your settings section. Out of the box the composition layer pins `enabled`, `temperature`, `maxOutputTokens`, `timeoutMs`, and `maxInputChars`, so those five values are the effective defaults even if you never open the settings page.
| Field | Default | Applies | Description |
|---|---|---|---|
| `enabled` | `true` | live | Master switch; off hides the button and disables the route |
| `strength` | `balanced` | next call | `conservative` — fix and structure only; `balanced` — also fill in implicit prerequisites; `aggressive` — rewrite as a full task brief (length ceiling relaxed to ~1500 characters) |
| `temperature` | `0.3` | next call | Lower keeps the rewrite more faithful to the original |
| `maxOutputTokens` | `2048` | next call | Output budget of one call; **truncated output is refused**, never written back, so raise this when using `aggressive` with reasoning enabled |
| `maxInputChars` | `12000` | next call | Input cap counted in Unicode code points (an emoji is one character); over-limit drafts are **rejected, never truncated** |
| `timeoutMs` | `60000` | next call | End-to-end deadline of one call; timeout surfaces as a distinct message, not a generic failure |
| `systemPrompt` | empty | next call | Your own strategy text; empty uses the built-in strategy |
| `strategyMode` | `replace-default` | next call | `replace-default` swaps the built-in strategy out entirely (its hard rules — preserve intent, never fabricate, body-only output, mirror the input language — are **not** retained automatically); `extend-default` appends your text after the built-in strategy, keeping those rules in force |
| `undoWindowMs` | `60000` | live | How long the undo affordance stays available (`0` = forever); the button reads it through the client settings mirror |
"Applies" mirrors the namespace's `applies: 'live'` registration: every field is re-read on each request, and
`enabled` / `undoWindowMs` additionally affect what the browser shows without a restart. Only changes to the
composition layer itself (a `cordis.patch.yml` edit) require reloading the profile directory, and `patchReload: live`
profiles pick those up on save too.
The same values can be set from the user patch layer without touching the plugin:
```yaml
# ~/.dsh/profiles/web/cordis.patch.yml (hot-reloaded on save)
- id: prompt-polish
config:
strength: aggressive
timeoutMs: 90000
```
**Model routing precedence** (host side):
1. an explicit route in the request body — the wire protocol carries one, and non-browser
callers may supply it; the bundled browser half does **not** send it, by design;
2. the route recorded in the current session's last request header;
3. the harness-wide default model (`agent-default-model`).
If none of the three names a route the plugin answers `unconfigured` with an actionable message
instead of guessing, which is exactly what happens in a brand-new session before any message has
been sent and without a default model. The bundled client therefore behaves as "session model,
falling back to the harness default".
## Error handling & edge cases
| Case | Behaviour |
|---|---|
| A local guard rejects the draft (empty or whitespace-only, attachment-only, `/command` or `@reference` chips, over `maxInputChars`, or a composer outside the `plain` phase) | Refused in the browser before any request is sent; the host route re-checks emptiness and length. The exact hover text per guard is listed below |
| Draft edited during the call | Result discarded, no write-back, button returns to idle |
| Send pressed during the call | The host route sees the disconnect, aborts the model call, and your original text is sent |
| Timeout | Mapped to a timeout message naming the configured seconds; retryable |
| Truncated output (`maxTokens`) | Refused with a hint to raise `maxOutputTokens` or shorten the draft |
| Model returns nothing usable | Normalized output empty → refused (`upstream`); retryable |
| Upstream model failure | Mapped to `upstream` with the provider's own text as the tooltip detail |
| Plugin disabled in settings | Button hidden; the route answers `403` |
| Profile has no `webServer` | The host half registers only the settings namespace and logs one line — headless profiles do not fail |
Every failure path leaves the composer exactly as you typed it. Error messages surface on the button itself (red icon + hover text) — the plugin never opens a dialog or a toast.
| Error code | HTTP status | Meaning | Hover text |
|---|---|---|---|
| `rejected` | `403 / 413 / 422` | Refused: disabled, body too large, malformed body, or invalid input | the host's own sentence (`请先输入内容` / `输入过长(n/max),请先精简` / `提示词优化已在设置中关闭`), else `无法优化这条输入` |
| `timeout` | `504` | The call exceeded `timeoutMs` | `优化超过 毫秒未完成` |
| `aborted` | — | Caller disconnected or pressed send; **no response is written** | `已取消优化` (the button returns to idle first, so this is rarely visible) |
| `unconfigured` | `409` | No model route could be resolved, or the composition lacks `llm` | `无法确定使用哪个模型:请先在会话里选择模型` |
| `upstream` | `502` | The model call failed, was aborted upstream, was truncated, or returned nothing usable | the provider's own message, or `优化结果在 token 上限处被截断…` / `模型没有返回任何文本` |
| `internal` | `500 / 502` | Invalid plugin configuration or an unexpected failure | the config error text, else `插件内部错误` |
Errors raised before a request is even sent come from the browser half and carry no code:
| Local guard | Hover text |
|---|---|
| Empty draft, or a draft made only of attachment chips | `请先输入内容` |
| Draft contains `/command` or `@reference` chips | `含 / 命令或 @ 引用的输入暂不支持优化` |
| Composer is not in the `plain` phase | `当前输入状态不允许优化` |
| The draft was edited while the call was running | `优化期间草稿已改动,结果未采用` |
| Anything unexpected in the browser half | `优化失败` |
## Architecture
```
dsh-prompt-polish/
├── package.json dsh.bundle.patch (layer) + dsh.client (browser half) + scripts/exports
├── cordis.patch.yml the composition layer: one insert row; its config is the settings base layer
├── src/
│ ├── index.ts host apply(): settings namespace + route + route resolution + error mapping
│ ├── enhancer.ts ctx.llm.stream call: deadline racing, cancellation, finish validation, normalization
│ ├── prompts.ts built-in strategy (SYSTEM/USER templates) + strength hints + output cleanup
│ ├── config.ts schemastery schema, defaults, input checks
│ ├── protocol.ts wire types and constants shared by both halves
│ ├── http.ts bounded JSON body reader + JSON responder
│ └── client/
│ ├── index.tsx browser half: three-state button, undo, slot registration, diagnostics
│ └── styles.ts button stylesheet (single injected
数据来源:公开的 DeepSeek Harness 插件目录与各插件 GitHub 仓库。本站为独立第三方目录,与 DeepSeek、幻方(High-Flyer)及插件作者均无隶属或背书关系。