dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH 界面两档模式插件:简化/原生一键切换,工具卡片白话化,降低上手门槛 / Two-level UI mode plugin for DeepSeek Harness
简体中文 | English
What this plugin set out to do, the DSH UI now does itself: the current interface ships folded tool-call groups, concise tool rows, and readable tool output natively. Both problems the plugin existed to solve are gone.
Development stops here. Final version v1.14.1 (2026-09-24); source, development records and acceptance material are kept in this repository as an archive. Never published to npm.
Not recommended as a finished plugin for new users; come back here only when you need something the official UI still lacks, such as an explicitly toggleable tier.
A three-tier UI plugin for the DeepSeek Harness web interface: toggle between Native, Tidy, and Plain with one click. Tidy keeps the product's own tool-row language, Plain rewrites it as plain speech, and both group tool calls into collapsed summaries. Beginners get plain-speak, power users get the full product — the same interface, read three ways.
After the DeepSeek Harness release, community criticism converged on one point: the learning curve.
DSH's bare-bones design is deliberate. But people who want a quick start and people who want the full feature set should not be forced into the same UI. dsh-prism answers that tension with three tiers:
| Mode | For whom | What the UI looks like |
|---|---|---|
| Plain | People who want to get going without learning the jargon | Tool calls are grouped and collapsed: the chain of calls before the final reply folds into a one-line summary; expanded, every row speaks plain language ("Reading a file") with emoji icons |
| Tidy | People who want a tidier flow without changing how they read the product | Same grouping and collapsing; expanded, every row uses the product's own row language: monochrome icon + category title (Bash / Read / Edit / Tool call) + "·" + argument summary, e.g. Bash · Run syntax check, Tool call · get_goal · {} — rendered with the host's built-in official ui-primitives components |
| Native | Power users who need complete information | Zero plugin takeover — the product renders exactly as shipped, pixel for pixel |
Native is the default. Switching takes effect immediately, and your choice is remembered: refreshing the page or restarting DSH keeps the tier you picked. Don't want it? Remove the plugin and the UI is back to factory state with nothing left behind.
sidebar.footer.action slot, sharing the row with WSL / memory entries); it shows the current tier initial (N / T / P) and opens the menu to switch between Native / Tidy / Plain, which also carries a "Hide complex tools" toggle (Plain tier only)@deepseek-ai/dsh-client-ui-primitives (DisclosureRow, StateDot, official icon components), with titles and summaries derived by the product's own toolRowModel rules — visually the same as a shipped tool rowStateDot) + monochrome icon + category title + "·" + argument summary; generic tools carry the tool name the way the product does (Tool call · get_goal · {})isError) shows red; a command that exited non-zero (a grep with no match, a truncated head pipe — routine cases) shows amber, captioned "command returned non-zero"TerminalBlock (command header + output + copy button), the read family the official ReadBlock (with line numbers); everything else keeps the delivery document (MarkdownText). When the official primitive is unavailable it falls back in place — nothing is droppedFoldToggle; this host version does not ship it yet, so the plugin's own fallback ("expand ▾ / collapse ▾") is used, and the official one engages automatically once the host upgradesdisplay (tool/retry seats, or a settled assistant seat that renders nothing), released per turn on a tier switch or unload — display rather than hidden, because the product unconditionally strips a same-named hidden attribute on its sideui-chat's ChatView.tsx: a ResizeObserver on the flow column that snaps the viewport to the new floor while the reader is pinned to the bottom) would otherwise pull the clicked row out from under the pointer. The plugin records the clicked block's on-screen position at click time and holds it, so the clicked row stays still while its content grows downward. The correction writes only the scroll offset, disarms 320ms after the layout settles (1.2s at the latest), and yields at once to any wheel / touch / scroll-key gesture; mid-transcript reading never moved, and the pin adds nothing theretoken / secret / password / api_key / authorization are never read — the Tidy tier filters them too; when a payload holds only sensitive keys the summary stays empty instead of falling back to raw JSONsk-xxx, Bearer xxx, ?token=xxx, key=xxx) are replaced with placeholdersfile_path and similar render as basename); the Tidy tier shows paths by the product rules, to stay close to the shipped rowstool-call / assistant-step / model-retry with a lower priority), and tier switches register / unregister dynamically and take effect instantly__PRISM_DIAG__; suspicious shapes are auto-logged (__PRISM_DIAG_LOG__() to read, __PRISM_DIAG_LOG_CLEAR__() to clear), so a problem that heals itself still leaves its scene behindpwsh → "running a command on the computer"); tools outside the table get an argument-name-derived summary. In the Plain tier every row inside a group renders by these rules; the Tidy tier uses the product's own category titles and summary rules (Bash · …, Tool call · name · …) and keeps the plugin filter only on the redaction floordsh.prism.mode), so a refresh or a DSH restart keeps it; other UI state (group open/close, menu, hide-complex toggle) stays in memory--dsw-alias-* design variables are used; adapts to both light and dark themesconsole.error('[prism] …') and land in the diagnostics surface (window.__PRISM_DIAG__) — never swallowedDeepSeek Harness is required first (Node.js 22.19+ or 24+).
The plugin is distributed through GitHub; clone this repository and install it by local path:
npx -y @deepseek-ai/dsh plugin --profile web add <path-to-this-repo>
You can also download published artifacts from Releases. After the Web UI starts, the tier entry appears at the sidebar foot, above Settings.
Is my tier remembered? How do I clear it?
Yes. Your tier is stored in browser local storage (key dsh.prism.mode), so a refresh or a DSH restart keeps it. To go back to "Native, no trace", just switch to Native; to remove the memory as well, run localStorage.removeItem('dsh.prism.mode') in the browser console and refresh. A different browser (or cleared browser data) starts from the default Native tier.
Why do some tool cards look unchanged?
Tools like read, write, and web_search already have polished official native cards; the plugin registers no replacement cards for them (Native mode is completely original). In Plain mode they are folded into the group alongside other tools and shown as unified plain-language rows — the official cards themselves are never altered.
Why do some status dots show red and others amber?
Red means the tool really failed; amber means the command exited non-zero (a grep with no match or a truncated head pipe counts — routine stuff), captioned "command returned non-zero". A run of piped commands no longer paints the whole fold red.
Does Plain mode affect how the agent works? No. The plugin only changes the display; the model receives and produces exactly the same input and output as in Native mode.
What do tables / code blocks look like in the detail panel?
Result text is redacted first, then handed to the official MarkdownText renderer (tables, code blocks, headings, lists, inline code, and links included); when the official primitives are unavailable it falls back to monospace plain text and never throws.
FoldToggle engage automatically)Ideas or tools that don't fit well? Open an issue and discuss.
exitCodeOf in the result section that shadowed the data-layer one; the newcomer returns undefined when no exit code is found, while the check reads code !== null — undefined !== null is always true, so every unmarked text (i.e. every normally successful tool) was judged "exited non-zero": nearly every row showed ✕ and the segment-level success count stayed at 0 (the ✓ n · ! m counter branch was unreachable — which is also the answer to the previous release's open question about that branch never firing)isError) is red — the v1.14.0 intent ("no more rows of red dots") now holds in both places (plain-language cards and the Tidy tier's official StateDot warning state)parseExitStatus exactly (leading newline + end-of-string anchor) — a [exit code: N] string appearing inside a file or a search result no longer counts--real-primitives); the new assertions cover row-level three states, the counter branch being reachable, the end anchor, and failures still going red; the reverse run against the buggy build turns exactly 3 sentinels red (row-level measured [0,0,2] — the two-red-crosses picture reproduced); the other gates pass 16/16, 26/26, 17/17, 13/13, 20/20; live check PASS with 1.14.1 served and zero errorsDisclosureRow, while three parts stayed hand-drawn: the row-end state dot, the fold toggle, and what the expanded row contains. This release replaces them:
StateDot (its running state is the same chasing animation as the one in the TerminalBlock header, so rows and cards never disagree)FoldToggle (button + aria-expanded; the caption moves from a made-up "expand" to the product's "N more rows")TerminalBlock (command + output + exit code + one-click copy + its own height fold), the read family through the official ReadBlock (a file block with line numbers)MarkdownText) — no content is dropped; unwired families must not impersonate an official blockcommand / file_path are pulled from the outer argument area (on the live app the command used to show up twice); the remaining arguments (cwd / timeout / offset …) stay as beforeerr, so a run of grep / head pipes (non-zero exits) painted every fold row red. Now only isError (a real tool failure) is red; a non-zero exit code goes amber (reusing the existing mixed state, captioned "command returned non-zero")path / offset / lines / totalLines and the like); the plugin layer cannot reach it, and parsing the text instead would invent content that looks right and is wrong (red line: never swallow a block, never invent one)PRISM_REAL_PRIMITIVES=1 does not survive WSL → Windows node.exe, so a --real-primitives flag was added--real-primitives modes, measured)dsh.prism.diag.log, capped at 20, oldest half dropped on overflow)span=false (the node fell into no span → the tool cell draws a standalone native row, which in the Tidy tier looks exactly like Native) and claimOK=false (the claim failed → nobody drew that span's fold line). The product folding its own turn (foldable) and nodes outside the window (inWin=false) are normal shapes and are not recorded — logging them would only drown the signal__PRISM_DIAG_LOG__() in the console returns the JSON, __PRISM_DIAG_LOG_CLEAR__() clears it; the dev/prism-claim-dump.js probe prints it alongside--client=dev/client-1.12.0-backup.js (the build without the logger) turns 5 of them red without breaking the run — the assertions bite. Live check: delivered version 1.13.0, tier and all three registrations healthy, the read control in place, the storage key absent while nothing is wrong; plus an end-to-end pass (inject a sample → read it back → clear)undefined, crashing the whole run — now it turns red as it should, without the crashdsh.prism.mode, the same route the host uses for its own UI preferences such as dsh.conversation.contentWidth). The read happens before the store is created, so renderers register from the remembered tier at startup. Unreadable, malformed, or blocked storage counts as "never stored" and falls back to Native; a storage failure logs one console.warn and never touches the errors counterdev/prism-persist-check.mjs (real React + real Chrome + real reload) 20/20; reverse verification: dropping the write → 8 red, reading a constant → 5 red. Regression: harness 145/145, seat contract 16/16, data red team 26/26, browser red team 17/17, pin check 13/13, self-check probe 12/12