dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DeepSeek Harness personal toolkit: runtime-toggleable quality-of-life optimizations, each shipped as one card in the Toolkit settings page.
English · 简体中文
Personal quality-of-life toolkit for DeepSeek Harness. Each optimization is too small to deserve its own vertical plugin, so they ship together as one entry in the Toolkit settings card: Web settings → Plugins → DSH-Toolkit expands a half-width sub-card grid (icon, title, short subtitle, on/off badge), and clicking a sub-card opens that optimization's dialog. The plugins page stays a compact launcher as the list grows.
Everything is wired through public extension surfaces — settings namespaces, the client module table, conversation slots, llm/stream, model discovery and webServer routes — so no dsh core package is patched and upstream one-click upgrades stay painless.
| # | Optimization | What it does | Default | Half |
|---|---|---|---|---|
| 1 | workspacelessChat | Keeps a no-project chat workspace and auto-connects it at cold start | on | client + host |
| 2 | editLastMessage | Edit-and-resend the last user message, rewinding the model context | on | client (host session.rewrite RPC) |
| 3 | viewActivity | Sidebar activity icon: running-first, then Today / Yesterday / Weekday / Earlier | on | client |
| 4 | slashI18n | Chinese descriptions for the / menu's commands and skills | on | client |
| 5 | changeReport | Codex-style per-turn file-change card at the turn tail | on | client |
| 6 | opencodeSession | Stable x-opencode-session header on OpenCode Go/Zen requests | on | host |
| 7 | modelCapability | Keeps one llm-pi-ai route's model list, capacities and image support current | on | host + client |
| 8 | smartReply | Gray ghost-text suggested reply in the composer, Tab to fill; registered routes runtime-transported (any protocol), context-aware, learns your phrasing locally | on | client + host |
cd ~/.dsh/profiles/web
pnpm add file:/path/to/dsh-plugin-toolkit # link:/path/to/dsh-plugin-toolkit while developing
# add "dsh-plugin-toolkit" to the dsh.profile.bundles list in package.json
systemctl restart deepseek-harness.service # or your profile's restart path
Server-side changes (this package) need a profile restart; the client bundle re-syncs with pnpm install inside the profile followed by a browser hard refresh. Toggles, paths and model lists are settings-namespace values, so day-to-day edits apply live without a restart.
The package is split into a browser half and a host half; both read the same toolkit settings namespace, and neither touches dsh source.
| Half | Files | Responsibility | Seams used |
|---|---|---|---|
| Client | client.js | Settings card, inline edit UI, sidebar re-sort, /-menu rewrite, change-report card, composer ghost-text suggestion, model-id pickers | Client module table, conversation node/turn-tail slots, conversation.input.overlay, inputActions, remote.* namespace wraps, settings scope |
| Host | index.js | Settings schema and defaults, default chat workspace directory, OpenCode session-header stamping | settings, llm/stream, globalThis.fetch |
| Host | model-sync.js + models-core.js | Model discovery wrap, sync/listing routes, capacity and modality enrichment, and the endpoint/credential resolvers suggest-api.js reuses for its unregistered-route fallback | llm model discovery, webServer routes, settings stores |
| Host | suggest-api.js + suggest-core.js | Suggestion route (runtime-first, direct fallback), session-context reads, and the pure prompt/salvage logic behind it | webServer routes, llm streaming, sessions, model-sync resolvers |
| Host | suggest-habits.js + suggest-store.js | Pure local phrasing statistics and their atomic, failure-tolerant persistence under <DSH_HOME>/plugins/dsh-plugin-toolkit/ | local filesystem |
Design rules that hold across the package:
webServer, no session.rewrite) disables just that optimization; the rest of the UI is untouched.toolkit settings namespace and is re-read at use time.
The card renders one sub-card per optimization with a live on/off badge and an Enable all / Disable all pair. Opening a sub-card shows that optimization's own description and fields (for example the chat directory, or the model-capability key and pickers).
workspacelessChatChat without picking a workspace first, like other agent harnesses' default-project behaviour. dsh's composer requires a blank session to belong to a workspace, so the optimization keeps a dedicated no-project chat workspace (「通用对话」) around: it is idempotently created with its friendly title whenever the optimization is enabled, so the option always shows in the sidebar and workspace picker — open it to chat without a project.
On top of that, when both baselines are ready, no session is selected, and the runtime's own startup policy has no recent workspace to connect (first run, no workspaces), the client also auto-connects a blank session in that workspace so the composer is live immediately.
The cold-start auto-connect failures retry at most 3 times (2 s apart) and then stay dormant until a list change re-triggers the check. The runtime's own recent-workspace auto-connect always wins; the auto-connect only fills the nothing-to-connect case, while the workspace itself exists unconditionally. The workspace is renamed to its friendly title only while it still carries the auto-derived basename (a title you set yourself is never overwritten).
| Config | Default | Meaning |
|---|---|---|
optimizations.workspacelessChat | true | Toggle the no-project chat workspace + auto-connect. |
chatWorkspacePath | "" | Host directory for the default chat workspace; empty resolves to <DSH_HOME>/chat. The server creates the directory (live on settings edits, too). |
chatWorkspaceTitle | 通用对话 | Display title of the no-project chat workspace. |
The card writes go through the toolkit settings namespace, so toggles and path edits apply live without a restart.
editLastMessageRetry a failed answer without copy-pasting and without polluting the model's context: the last user message's hover actions gain an Edit button. Clicking it opens an inline editor prefilled with that message; Save & resend rewrites the session in place — the edited message and everything after it are replaced (the model context truly rewinds, so the next request contains only the edited content), and a new turn answers the edited message.
This needs host support for the session.rewrite RPC (a small addition to this repo's dsh checkout). On hosts without it, the button still shows but reports that rewriting is unsupported. The client transcript erases the old message and its failed turn; turn boundaries are kept so the failed turn collapses into an invisible empty turn. Requires an idle session (a running turn rejects with agent-busy); only the last human user message is editable, text-only.
The host extension lives in this repo as host/session-rewrite.patch. A dsh upgrade drops it, so re-apply it after every upgrade: node scripts/apply-host-patch.mjs, then pnpm run build:lib && pnpm run build:web and restart the harness. Verify the live behavior with node scripts/e2e-verify-rewrite.mjs.
| Config | Default | Meaning |
|---|---|---|
optimizations.editLastMessage | true | Edit-and-resend button on the last user message. |
viewActivityAdds an Activity icon (native clock glyph) to the workspace sidebar, right after the search (magnifier) control. Clicking it re-sorts the sidebar's conversation list in place: running conversations surface in a leading Priority group, then the remaining history grouped by Today / Yesterday / Weekday / Earlier (newest first within each group) — the familiar "recent conversations" pattern. Clicking again restores the previous grouping (workspace sections or the flat list); the icon highlights while the activity sort is on.
100% non-invasive external implementation: hooks the activity toggle into the sidebar header and dynamically swaps the session tree, with zero modifications to official DSH core packages and zero conflict with upstream one-click upgrades.
| Config | Default | Meaning |
|---|---|---|
optimizations.viewActivity | true | Workspace-header activity icon + in-place running-first / by-day re-sort. |
slashI18nThe / menu's shell copy (group titles, the user-only badge, skeleton rows) is already localized, but the entries' descriptions come straight from the host: built-in command descriptions, argument hints, and skill catalog descriptions all render verbatim in English, even under the Chinese UI. This optimization translates them client-side: the toolkit wraps the remote.commands.list and remote.skills.list namespace methods and rewrites each response's description / input.hint through an exact-match en→zh dictionary before any consumer reads it.
Scope and safety rails:
name fields are never translated — fuzzy matching, the draft-chip lexicon, and claim adjudication all read them; whenToUse / modelInvocable ride untouched too.description in their SKILL.md frontmatter — no dictionary needed. The dictionary only covers what ships with dsh (6 built-in commands, 2 hints not already keyed by ui-conversation's own hint.* locale keys, and the 2 built-in skills).| Config | Default | Meaning |
|---|---|---|
optimizations.slashI18n | true | Chinese descriptions for the / menu's commands and skills. |
changeReportA codex-style change report at the tail of every completed turn. When a turn changed files (edit / write / mutating str_replace_editor calls), the tail renders a compact card: "N files edited · +A -R", one row per file with its own +N -M (click a row to open the file in the viewer), a show more expander beyond four rows, and a Review button opening the turn's full diff. The card is a superset of dsh's built-in produced-files tail: it joins the same conversation.chat.turnTail chain ahead of it (lower priority) and declines on non-mutating turns, so the stock tail renders exactly as before.
How it works, and its bounds:
run_code's nested sub-calls (which log as tool/code-dispatch events keyed by their root call). Line counts reuse the primitives' diffTotals, so the header numbers always match what the review DiffBlock renders.bash-side file writes (sed, redirects, …) are invisible to the transcript and therefore not tracked — the report covers the dedicated file-mutation tools, like codex tracks its own.| Config | Default | Meaning |
|---|---|---|
optimizations.changeReport | true | Per-turn change report card at the turn tail. |
opencodeSessionOpenCode Go now 400-rejects requests that lack x-opencode-session ({"type":"MissingSessionID", ...}), and its guidelines ask coding agents to identify themselves with their own user agent and to send one stable session id per conversation so the gateway can route requests and reuse prompt caches. The user-agent part is already satisfied — every dsh provider request carries deepseek-harness/<version> attribution. This optimization fixes the session half, entirely from the plugin side: an llm/stream listener carries the in-flight request's session identity down the async chain via AsyncLocalStorage, and a thin boot-time globalThis.fetch wrap stamps the header onto any request whose target host is opencode.ai or a subdomain (Go and Zen alike).
How it works, and its bounds:
MissingSessionID.viewActivity: zero modifications to dsh core packages.| Config | Default | Meaning |
|---|---|---|
optimizations.opencodeSession | true | Stable per-conversation x-opencode-session header on OpenCode Go/Zen requests. |
modelCapabilityKeeps every llm-pi-ai route's model list current against its endpoint's live listing: newly served models become selectable immediately, with no restart; missing context/output limits are filled from the models.dev registry and from sized siblings; image input is borrowed from any other registered provider that declares the same id multimodal; and a model can be forced vision-capable or text-only. This was the 「模型能力」 section of dsh-plugin-quota-badges and now lives here in full — the other plugin no longer ships it.
How it works, and its bounds:
modelsRouteKey: the host answers a catalog route from its installed catalog without any network call, so a vendor's newer models would otherwise stay invisible on every other route. Each route resolves its own endpoint (request draft → the route's stored baseURL → a modelsRouteBaseURLs override; the primary route additionally falls back to modelsBaseURL), credential (draft key → the route's apiKeyEnv through the credentials service, then the environment; the primary route additionally falls back to modelsApiKey) and models.dev directory (the primary route uses modelsRegistryProvider, every other route its own key). A route with no resolvable endpoint, or with neither a credential nor deployment headers, keeps the host's answer — the wrap never guesses a URL or probes into a guaranteed 401. The endpoint must be resolved before the host's own discovery runs: a provider pi-ai ships no catalog for (clinepass and any gateway the adapter does not know) makes the host throw DISCOVERY_FAILED when the request names no baseURL — and the GUI's request names only the route. Because the wrap used to call the host first, that error escaped before the endpoint could be supplied and the route answered with whatever the editor had saved. The wrap now resolves the endpoint first and, on that refusal, retries the host with the resolved endpoint (the host's reply is the endpoint's own listing, so it is reused and tagged live rather than probed a second time); only when no endpoint resolves at all does the host's error propagate unchanged. Diagnose a route that will not fetch with DSH_TOOLKIT_DEBUG=1: the wrap prints the branch it took (no endpoint / no credential / probe result / host refusal / host listing reused) to stderr, with credential lengths but never credential values. The wrap is tagged enrichedByToolkit: only an answer whose probe really succeeded counts as a live listing for the sync route, so a transient probe failure falls back to the catalog and can never shrink the stored route. There is no "sync model list" button on the card: adopting new models happens in dsh's own model settings (click "fetch available models"; the list is yours to confirm before it is written), so nothing dumps the whole live listing into the route by accident. The server route that does it — POST /api/toolkit/sync-models — stays registered and only runs when explicitly called (API/scripts); it still targets modelsRouteKey alone.GET /api/toolkit/models (modelsRouteKey's stored rows ∪ the runtime's listModels, deduped and sorted, in-process reads only — no network) and are fetched once per dialog open: it only reads the configured models and changes nothing. The two lists are mutually exclusive: adding to one removes the id from the other. The pickers and both forced lists stay scoped to modelsRouteKey, unlike the multi-route discovery wrap.api) so the configuration surface's own save passes serviceability too, and heals image input on already-saved models (that save path sends no input field). Edits to the forced vision / text-only lists apply to the stored models at once — no restart, no re-sync.modelsApiKey, the former quota-badges namespace donates its OpenCode key, forced vision / text-only lists, and route shape; the migration marker lands in the same write, so clearing the key afterwards is never re-filled.| Config | Default | Meaning |
|---|---|---|
optimizations.modelCapability | true | Master switch for the discovery wrap, sync route, and capability healing. |
modelsApiKey | "" | OpenCode API key; empty falls back to the environment variable. |
modelsApiKeyEnvVar | OPENCODE_API_KEY | Environment variable consulted when the key is empty. |
modelsRouteKey | opencode-go | The primary route: what the sync route, the pickers and the forced vision/text-only lists act on; the discovery wrap keeps modelsBaseURL/modelsApiKey as its fallbacks. |
modelsBaseURL | https://opencode.ai/zen/go/v1 | Endpoint probed for the primary route's live listing (every other route uses its own stored baseURL). |
modelsRouteApi | openai-completions | Wire protocol pre-written onto the primary route (other routes use their own api, defaulting to openai-completions). |
modelsSyncPath | /api/toolkit/sync-models | Same-origin explicit-sync route (no card entry point; API/scripts only; changing it needs a restart; syncs the primary route only). |
modelsPath | /api/toolkit/models | Same-origin route listing the picker's candidates. The server registers the path once (a change needs a restart) while the card re-reads it live, so until that restart an edit makes the card t |