dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
DSH Web UI enhancement plugin: turn-by-turn navigation, thinking-chain default-expand & session cost meter for the official DeepSeek API
dsh-web-enhance is a browser plugin for the DSH (DeepSeek Harness) web UI that adds small quality-of-life features. It currently ships:
5h额度18%,周额度35%,周窗口40%,安全线52%,5d15h后重置 — quota usage of both windows, the weekly window's time progress, the fixed-overhead safety line, and the weekly reset countdown; the badge text is Chinese). Hover for each window's time-to-reset, the safety-line math and more. The numbers are account-level truth fetched by the host while GLM streams; zero queries otherwise.Migrating from dsh-task-notify? That plugin is retired — its completion alert now lives here as a built-in feature (same decision rules, plus an on/off switch in Settings → General). Switch over with:
dsh plugin --profile web remove @yangzhe1991/dsh-task-notify dsh plugin --profile web add @yangzhe1991/dsh-web-enhancethen restart the Web GUI and refresh the tab. This plugin's other features come along for free.
useSessions: ids / byId.origin), the per-session UI status (useSessionStatus: agent running + pendingInteraction) and on-demand job rosters from the jobs client service (watchRows → state.rows). On dsh ≤ 0.1.6 the completion alert simply stays off (the rest of the plugin is unaffected) — those versions carried job rosters inside the session-list snapshot and dialogs behind useSessionPendingInteraction, both removed in 0.1.7.useChat hook and trajectory as useTrajectory; both are adapted, with the legacy paths retained for older dsh. Verified against dsh 0.1.3-alpha.2 since 0.1.9, against dsh 0.1.5-rc.2 since 0.1.10 (the release that adds "Open files in the system app", verified end-to-end on a real desktop), and against dsh 0.1.7-rc.2 since 0.1.11 — dsh 0.1.7 replaced the per-request provenance field with requestConfig/providerMetadata in the trajectory snapshot, which had silently hidden the cost meter; 0.1.11 reads all three shapes, so the meter keeps working from 0.1.2-alpha.4 onward. 0.2.0 (completion alert) is verified against dsh 0.1.7-rc.2. 0.2.1 is verified against dsh 0.2.0-rc.1. 0.2.2 (GLM quota badge) is verified against dsh 0.2.1-alpha.2 — its quota channel rides on the llm/stream waterfall, settings volatile-field relay and remote.settings.describe, all official 0.2.x surfaces; the quota badge needs dsh ≥ 0.2.0-rc.1, everything else is unaffected (on older hosts the badge silently stays off). 0.2.5 (safety line) is verified against dsh 0.2.1-alpha.2.@deepseek-ai/dsh-* peer range against the runtime version and skips any plugin whose ranges do not match (^0.1.7-rc.2 / ^0.1.3-alpha.2 exclude 0.2.0-rc.1). No plugin code changed: 0.2.1 only widens those ranges (^0.1.x || ^0.2.0-rc.1). On 0.2.0-rc.1 an already-installed 0.2.0 can also be admitted without upgrading, by granting the exact-version exemption (dsh plugin --profile <profile> allow-version @yangzhe1991/dsh-web-enhance@0.2.0 --dsh-version 0.2.0-rc.1 --accept-risk).
dsh-client-ui-primitives (IconChevronUpOutline14 → IconChevronUpOutlineMedium). 0.2.0 uses the current names; older releases render an empty page on 0.1.7+.z.any().volatile() while the module loads, but Schema.prototype.volatile only exists since schemastery 3.18.4. The package had never declared @deepseek-ai/schemastery, so under registry installs the import could resolve to a stale shadowing copy (3.18.2), crashing the node half at import time — the host then reports the plugin as "failed to import". No code changed: 0.2.4 only declares the missing peer ("@deepseek-ai/schemastery": "^3.18.4"). Verified against dsh 0.2.1-alpha.2 (build output is byte-identical to 0.2.3).安全线 segment plus hover math): the line is weekly quota − fixedDaily × days-to-reset; crossing it means the weekly fixed overhead (always-on automation measured at ≈12.5k credits/day from the 2026-10 billing export) can no longer be covered before reset. New plugin config field fixedDaily (default 13000) ships inside the quota payload, which bumps to v2 — older payloads are rejected rather than rendered without the budget. Also moved the probe's captured API fixture from /tmp into probe/fixtures/. Verified against dsh 0.2.1-alpha.2.assistant/attempt event, but the stock frontend treats that event as invisible — the transient streaming rows are retired, nobody claims the event, and the on-screen thinking/body simply vanishes. This plugin registers its own conversation node definition for that event, expands the compact stream records into the same "safe interrupted prefix" the server uses (BlockAssembler.interruptedBlocks semantics — equivalence verified case-by-case plus 300 fuzz sequences), and emits an official-assistant-step-kind view node so the stock renderer draws it (markdown, thinking disclosures, "Stopped" badge). Verified against dsh 0.2.1-rc.2 (assembly probe 17/17: live settlement, history replay, retry chains, empty attempts, key stability). The feature needs dsh ≥ 0.2.0-rc.1 (the uiConversation.events definition-registration surface); on older hosts it silently stays off without affecting anything else.💡 Thinking chain default-expand — the floating lightbulb button in the bottom-right corner (highlighted when on), sitting above the built-in "scroll to bottom" button:
💰 Session cost meter — on the same line as the built-in stats line, at its far left, e.g. ≈ ¥0.83 · 2 turns 12 steps | …:
deepseek-official); it disappears once you switch to another API.deepseek-flash and deepseek-v4-pro; the retired names deepseek-v4-flash / deepseek-v4-flash-vision-exp are still callable and are billed at Flash rates, so they are priced the same), priced per request at its actual time with peak/off-peak rates (peak = Beijing time Monday–Friday 09:00–12:00 and 14:00–18:00, twice the off-peak price; weekends are off-peak all day), with cache-hit input at the discounted rate.≈ prefix (hover for details: model, tokens, peak/off-peak request counts, unknown models). Clicking "Load earlier" to page that history in turns the remainder exact.src/client/cost.ts whenever the official pricing page changes.📂 Open files in the system app (on by default) — clicking a file no longer pops the right sidebar:
ctx.sidebarRight.openResource(...) in the shipped source, with no setting to disable it), so glancing at a file means the sidebar pushes in and squeezes the conversation.session.openWorkspacePath, which opens the file in your desktop default application — the same thing as double-clicking it in Finder/Explorer. The conversation column stays put; the sidebar stays closed.🔔 Completion alert (on by default) — a chime when the work is really over, and a tab-title alert while you are on another tab:
running/stopping, or while a dialog is waiting for you. When it stops being busy and stays that way for ~2 seconds, the chime rings once — and only once.
ask_user_question prompt, or a plan review. Same chime as above; the tab title distinguishes the two.failed or killed during that busy period.document.hidden), the title becomes 🔔 N session(s) finished — <original title>, or 🔔 waiting for your choice — <original title> when a dialog is waiting. Switching back restores it.🪙 GLM Coding Plan quota badge — when a session runs on GLM, the cost-meter spot shows the subscription quota usage instead:
zai-coding-cn / zai / zhipu, or any model id starting with glm); switching back to DeepSeek restores the ¥ meter. The two gates never mix.5h额度18%,周额度35%,周窗口40%,安全线52%,5d15h后重置 (5h quota 18%, weekly quota 35%, week-window elapsed 40%, the fixed-overhead safety line at 52%, weekly reset in 5d15h). Comparing the weekly quota against the week window's time progress tells whether you are burning the week ahead of its clock. Hover adds each window's time left until reset, the safety-line math (reserve, headroom, an over-the-line warning), the plan tier and the data timestamp.weekly quota − fixedDaily × days-until-reset; usage above it means the reserved budget can no longer cover the fixed overhead even if everything else stops immediately. fixedDaily (credits/day) is a plugin config field, default 13000; override it via the plugin row's config: { fixedDaily: … } in your profile's cordis patch. The value ships inside the quota payload (v2) and the line is recomputed live on every render.GET /api/monitor/usage/quota/limit, Bearer auth) — the same numbers as the official usage page, including consumption by other tools such as Claude Code. The key is resolved from the credential store with automatic endpoint pairing (ZAI_CODING_CN_API_KEY → open.bigmodel.cn, ZAI_API_KEY → api.z.ai); the key never reaches the browser.document.documentElement.dataset.dshWebeQuota (remote readiness, describe outcome, payload age).Dropped-stream keep registers a conversation node definition (ctx.uiConversation.events.register) — not a slot, no React of its own: the definition subscribes to exactly one event type (assistant/attempt, table-form match, zero dispatch overhead for everything else), start expands the compact stream records in the event data and reassembles visible blocks under the server's own rules, and buildViewNode emits a node of the official assistant-step kind — renderer dispatch looks nodes up by kind in the conversation.chat.node slot, so the official AssistantNodeView renders it as-is (markdown, thinking disclosures and the "Stopped" badge are all stock drawings). The anchorSeq is the attempt event's durable seq, which sorts naturally before step/end and the turn/error row; node keys stay stable via "definition kind + event seq". Assembly rides ctx.inject(['uiConversation'], …) as a soft dependency (a missing service only silently disables this one feature) with state published to dataset.dshWebeAttemptKeep. Equivalence of the reassembly with the server's BlockAssembler.interruptedBlocks, and the behavior of "definition × real assembler", are verified by probe/attempt-keep.logic.test.mjs (313 cases incl. 300 random fuzz sequences) and probe/attempt-keep.assembly.test.mjs (17 cases: live settlement / history replay / retry chains / empty attempts / key stability, loading the real ui-conversation bundle and this plugin's real build artifact).
The GLM quota badge is this plugin's first host-side code, forced by a hard constraint: the quota API needs the API key, and the key lives only in the host credential store (the credentials service never returns secret values to the browser by design). The whole path rides official surfaces — the trigger is an llm/stream waterfall listener (the host already sees every model call; chunks pass through untouched via yield* next(), and the async generator's finally covers normal end, throw and cancellation — the same pattern as the official session-checkpoint-policy); the relay is a volatile field (glmQuota) on this plugin's settings entry, written only when the payload meaningfully changes (every settings write goes through a profile-patch file rewrite plus config reconciliation); the read is the browser half polling remote.settings.describe() every 5 seconds, only while a GLM session is on screen. Payload validation and badge/duration formatting live in quota-format.ts (dependency-free pure functions, verified offline by node probe/glm-quota-client.test.mjs); the trigger/dedupe/write chain is replayed with the real captured API response in probe/glm-quota.test.mjs.
⚡ Dropped-stream keep — when the provider cuts the stream mid-output, what already streamed no longer vanishes:
assistant/attempt event — the data survives — but the stock web frontend treats that event type as invisible: the transient streaming rows are retired, no renderer claims the event, and the thinking/body on screen disappears together, leaving only an error row.assistant/attempt events, expands the compact stream records inside the event, reassembles the thinking/text blocks under the server's own "safe interrupted prefix" rules (tool calls are always dropped — they were never dispatched before the cut, and a call without a result would only mislead), and emits a view node of the official assistant-step kind so the stock renderer draws it: markdown body, thinking disclosures and the "Stopped" badge all behave exactly as usual.assistant/message path (which already renders them) — no duplication.document.documentElement.dataset.dshWebeAttemptKeep ({"registered":true} means the feature is live).dsh plugin --profile web add @yangzhe1991/dsh-web-enhance
Restart the Web GUI (Ctrl+C the dsh web process and run it again) and refresh the browser tab. (dsh plugin runs pnpm add and auto-appends the bundle to dsh.profile.bundles.)
For local development, install from a path instead — the link: spec keeps a live symlink so edits take effect after a rebuild + restart:
dsh plugin --profile web add link:/path/to/@yangzhe1991/dsh-web-enhance
The thinking-chain default-expand works on the official reasoning rows ([data-variant="think"] with a [data-disclosure-row][aria-expanded="false"] row inside). Because the full reasoning text is only mounted when a row is open, the plugin clicks each collapsed row to flip the built-in component's own React state. A MutationObserver on document.body watches for newly mounted rows (streaming output, new turns) and expands them as they appear; a one-time sweep covers rows that are already rendered. Only added subtrees are scanned, so a row you collapse by hand is left alone.
The session cost meter registers into conversation.composer.dock (the same slot as the built-in stats line, sharing its row as the first element). It combines two data sources: the trajectory view (per-request provider/model/usage/timestamp/startSeq — the basis of the exact pricing) and the tokenUsage projection (whole-session token totals). The per-request provider/model has moved across dsh versions (provenance up to 0.1.5, requestConfig/providerMetadata from 0.1.7), so requestRoute in cost.ts reads all three shapes — a version that renames the field again shows up as an empty line, which is why the component also publishes its decision to document.documentElement.dataset.dshWebeCost (window request count, how many are deepseek-official, the latest route, the total, whether it rendered). Every request observed with usage is persisted into a per-session accumulator keyed by startSeq (localStorage, last-wins so retried usage replaces instead of double-counting); the projection's remainder beyond the accumulator (history that was never