Archived-session management, task-stop notifications, an update checker, model routing (keyword rules + allowlist-gated model_route), multi-task scheduled triggers (heartbeats and fixed-time tasks), and a DeepSeek-style message scroll nav for the dsh web GUI.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-better
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:wackyju2-beep/dsh-better
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
Unofficial community plugin. Not affiliated with DeepSeek. Code produced by AI.
Better DSH — a dual-half plugin (host + browser) that follows the DeepSeek Harness plugin conventions and adds a few handy tools to the dsh web GUI: archived-session management, system-level task notifications, an update checker, model routing, multi-task scheduled triggers (heartbeats & fixed-time), and a DeepSeek-style message scroll nav. Everything lives under Settings → Better DSH, ready to use right after install.
中文 · English
This plugin is listed on dsh-market — you can discover and install it right from the marketplace.
Install
Published on npm — one terminal command installs it into your dsh profile:
dsh plugin --profile web add dsh-better
Restart DSH afterwards and the "Better DSH" entry appears in Settings.
Features
Settings → Better DSH:
- Archived sessions — list every archived conversation (title hints, working directory, archived time, local log state); restore it to its original workspace slot and keep chatting, or delete it for good together with its local log;
- Task notifications — while the page stays open, a stopped task raises a native system notification (Windows toast / macOS / Linux desktop notification): agent question or options, task finished, task errored — each trigger individually toggleable;
- Update checker — compares your local dsh against the latest GitHub release (full semver rules); copy-ready update commands, or pop up an independent terminal at your checkout and paste them yourself;
- Model routing — keyword rules match user messages in order; the first hit switches the session to the target provider / model / reasoning effort; an optional
model_routetool lets the agent switch models mid-conversation (design ported from dsh-model-router, trimmed to what was needed); - Scheduled tasks — multi-task scheduling: create any number of interval heartbeats (inject every N minutes, OpenClaw-style) and fixed-time triggers (daily / weekly / monthly at HH:mm); every task carries its own name, switch, schedule, prompt, and target; targets are the main workspace root (internal patrol) or any specific session. Everything runs inside the dsh backend process — no browser needs to stay open;
- Message scroll nav — a 1:1 port of the chat.deepseek.com scroll nav: one tick per message you sent, hover to preview, click to jump; colors customizable in its settings page. Off by default; when enabled it takes over from the official built-in turn navigator, and toggling it off restores the official one.
Every configuration change applies live — no restart needed.
How it works
A standard dual-half plugin: the host half runs inside the Node backend process and registers a set of loopback-only /api/dsh-better/* exact API routes; the browser half loads per page, injects the settings UI, and talks to the host over those routes.
Everything is resolved against the running dsh runtime at load time: dsh-better is loaded through a symlinked directory and cannot statically import "@deepseek-ai/*", so the host half resolves the host modules it needs with the running entry as the anchor — the same build artifacts as the running instance. Any missing piece disables just its own feature with a warning instead of breaking backend startup.
Three security fences: loopback peers only; a Host header check (anti DNS-rebinding); and POST bodies must declare application/json — a cross-site "simple request" cannot forge that header, and these routes never answer CORS preflights, so a forged page can't touch your archives.
Archived sessions
- Restores/deletes write straight through the open workspace persistence layer. Archiving never touches workspace bookkeeping, so restoring is exact: remove the session from the global archive set and it lands back in its original workspace slot;
- deleting wipes the local
.jsonlsession log and removes the session from the workspace records and the archive set; a live session refuses deletion (session-live); - every change writes straight into the persisted workspace domain, and the browser converges instantly through the gateway's existing
domain/changedforwarding — every open page, no manual refresh.
Task notifications
- Built on the standard browser Notification API — cross-platform by construction (Windows toast / macOS Notification Center / Linux freedesktop desktop notifications, with the DeepSeek whale icon); notifications naturally stop when the page closes, an inherent boundary of that API;
- the notification engine observes the event stream by wrapping two frame entry points of the session runtime, reversibly: it only watches from the side and never alters dispatch; stopping the plugin restores every wrapped prototype method and clears internal state (the wrapper carries a marker so re-enabling can't stack layers), leaving no stray observers behind;
- a session that dies while its question is pending no longer swallows completion notifications forever (pending registrations clear when a new run starts); subagent child sessions don't re-notify by default;
- permission, the master toggle and the three trigger toggles live in browser-local localStorage.
Update checker
- Directory discovery chain (memoized in-process): explicit
DSH_BETTER_REPO_ROOT→ walk up from the running entry (argv[1]) and cwd to the nearest@deepseek-ai/dshpackage.json (hits both source runs and packaged installs) → the pnpm global store next to the node executable → a conventional-path scan of<drive>:\.dsh\deepseek-harness. A path containing node_modules means "packaged install"; an apps/cli layout means "source build"; the drive scan and pnpm probing are Windows-only and silently skipped on unix; - three-tier release sources, first available wins:
/releases/latest(that endpoint excludes prereleases — a 404 is expected when every upstream release is a prerelease) → the/releaseslist's first non-draft entry (one retry) → thegithub.com/<repo>/releases.atomfeed (a different host — the independent path when api.github.com is down entirely; one retry). Per-request timeout of 9 seconds; successes cache for 5 minutes; if everything fails within 24 hours of a success, the stale value is shown with a "cached data (may be outdated)" badge — stale data beats a blank error; - update checks run off the shared serial queue, with concurrency dedupe and a failure cache: the panel keeps working when GitHub hiccups, and nothing else gets blocked;
- version comparison follows full semver rules (
0.1.1-rc.2 < 0.1.1, numeric prerelease identifiers compare numerically); the comparator is exported separately for testing; - terminal popup: on Windows,
cmd /d /s /c startgives the inner cmd a brand-new console — dodging the pitfall wheredetached: trueequalsDETACHED_PROCESSand a console program gets no console at all; macOS usesopen -a Terminal; Linux tries x-terminal-emulator / gnome-terminal / konsole / xfce4-terminal in order, using a 250ms error-race to tell a "missing program" from a real launch, with late errors parked on a no-op listener so they can't take the backend down. The window is fully independent of the backend lifecycle, never managed or killed by it; you paste the commands yourself — the plugin never types for you.
Model routing
- The engine's pure functions — rule matching, target validation — are line-for-line identical to the original dsh-model-router; configuration persists in its own settings namespace
better-model-router(deliberately distinct from the original plugin, so the two can coexist); - rules match user message text in order, first hit wins; a miss changes nothing. Before anything is written, the target is exact-validated against the live DSH registry (provider must be active, model must resolve, reasoning effort must be supported); a failed validation writes nothing; dormant targets can be saved but never execute;
- subagent sessions don't apply session-header selections by default; the engine lazily installs the official selection assembly so keyword rules take effect on the subagent's first request;
- the optional
model_routetool (off by default): once enabled, the agent may switch the current session's model mid-conversation, strictly within combinations listed one by one in an allowlist, and every execution is re-validated live; with an empty allowlist no tool is registered; the chat stream shows a matching routing card; - read-only routes run off the shared serial queue, rule targets validate in parallel, and upstream requests carry timeout caps — opening the routing page never drags the rest down; saves use a version-number optimistic lock and clearly report conflicts when the config changed in another window.
Scheduled tasks (multi-task scheduling)
One page manages any number of tasks (capped at 20), each with its own name, switch, type, schedule, prompt, and target, sharing one delivery path. The scheduler runs entirely inside the dsh backend process — it keeps firing with the browser closed:
- two types: "interval heartbeat" injects every N minutes (min 5, default 60), counting from the moment it is enabled; "fixed time" fires at HH:mm daily / weekly / monthly (local time zone, at most once per minute); months lacking the configured date (Feb 30) are skipped, never overflowed into the next month;
- create / delete: a "New task" button next to Save appends one task; each task card has its own "Run once" for end-to-end verification (manual firing works even while disabled) and a two-step-confirm delete;
- two target kinds: "main workspace root (internal patrol)" = inject into the newest live root sessions under that workspace (capped at 5; when none is live, the newest one is woken up); "specific session" = inject into that session only. A target that is not in memory (fresh backend restart, never opened in a browser) is cold-resumed through the official
sessionControllerresume path and then injected — exactly equivalent to opening it in the web app and sending a message yourself; the accepted cost is that woken sessions stay resident; - prompts support the
{time}placeholder (replaced with the delivery time) and fall back to a default patrol prompt when blank; messages carry a plugin source, the same delivery mechanism the official schedule reminders use; - the fixed time is picked from hour/minute dropdowns; the session-target dropdown shows conversation titles by default (same source as the archive page, with a short-code suffix) and toggles to a path+code compact mode; every dropdown uses the native dsh Menu style and tracks light/dark themes;
- the page shows a per-task countdown to the next fire plus a shared log of the last 20 runs (task name, injection result, failure reason);
- firing consumes tokens: an injected session really runs a model turn — size your intervals accordingly.
Three implementation notes:
- config & migration: the task table lives as a whole in a dedicated settings namespace
better-scheduler({ version: 2, tasks: […] }, capped at 20); saving uses a version-number optimistic lock and clearly reports conflicts when the config changed elsewhere; the host subscribes viascope.watch, so every task re-arms live after each save — no restart. Old single-task configs (separate heartbeat / fixed-time entries) migrate automatically into the task list on read — configured ones keep their original timers, never-configured installs migrate to an empty table; - scheduling core: a minute-level tick reconciles all tasks every minute; each task owns a private ledger (last run, next fire, fixed-time minute-dedupe key), and the timing baseline resets only when a task first appears or flips off → on — editing its name/prompt/target never restarts its rhythm, and deleting a task clears its ledger. "Every N minutes from the moment it's enabled", "at most once per minute" and "missing dates are skipped" all derive from this ledger — no browser required, no system cron either;
- delivery path: every task shares one delivery chain — resolve the target sessions, fetch their agents through the official
sessionController.resolveAgent(cold-resuming the standard way when a target is not in memory), then inject the user message viaagent.followup; a main-workspace-root target matches live root sessions by cwd (capped at 5) and delivers to each. "Run once" on the page walks this very chain — an end-to-end rehearsal of the production delivery.
Message scroll nav
The official build now ships its own (off by default since v0.4.1): dsh 0.1.2-alpha.1 includes a built-in turn navigator (the per-turn tick rail at the conversation's right edge). This feature became an optional enhancement — off by default, leaving the official navigator untouched; once enabled, the takeover is global: the official navigator stays hidden in every conversation, regardless of whether this feature's tick rail is currently on screen (in a session with fewer than two ticks or with messages not yet paged in, only this feature's rail stays dormant — the official one remains hidden); toggling off restores the official navigator everywhere, instantly. The toggle applies live — no page reload needed. Jumps default to the official mechanics (one instant position write, honored by the official scroll ledger — never dragged back by streaming output); an optional "Smooth scroll jumps" toggle in settings restores the pre-alpha.1 glide with its heartbeat guard (during active streaming it may occasionally need an extra beat to settle).
- The scroll nav's class names and layout were reverse-engineered from chat.deepseek.com's production stylesheet and rebuilt 1:1 as a blurred pill track;
- ticks derive straight from the conversation snapshot: each
user/steering(mid-run interjection) chat node maps to one tick, aligned by a stable anchor key onto the rendered message row; - one long-lived listener maintains every DOM-derived bit: the scroll listener binds once via capture-phase delegation (scrolls from any element reach it, and a scrollport swapped in mid-session retargets on its very first scroll), with a
ResizeObserverfollowing layout changes; the currently-read position is computed synchronously on every scroll — deliberately no rAF, which freezes in backgrounded windows and would leave the active tick stale; - clicking a tick scrolls the conversation scrollport onto the matching message row instantly — the same mechanism the official turn navigator uses (a single scrollTop write; the official scroll ledger classifies it as programmatic rather than reader input and disengages bottom-follow on its own); the old 5-second heartbeat guard retired together with the official scroll-system rewrite;
- hiding the official navigator never relies on build-time class names (officials are CSS-module hashes) — only the
navelement inside the official scroll container; the plugin's own rail carries a dedicated marker and can never be caught by its own rule; - theme colors ride CSS
light-dark()to follow light/dark automatically; custom colors persist in browser-local localStorage and apply live.
And something we're a little proud of: the UI follows dsh's original design language throughout — colors, radii, spacing and typography all come from the active theme's semantic tokens, with zero third-party styling. Whatever theme you switch to, it blends in like a built-in page.
Feedback
Running into anything — install trouble, UI glitches, silent notifications, routing not kicking in… — or just have an idea? Please open an Issue; more reports are always welcome. The more specific the report (DSH version, OS, steps to reproduce), the faster the fix.
Compatibility Notice
Until the official host reaches a stable release, this plugin supports exactly one host version: DSH 0.1.2-rc.1 (including post-rc.1 master builds that self-report this version). engines.dsh in package.json is pinned to this exact version, so dshmarket will flag every other host version as incompatible. The 0.1.3 alphas currently break core functionality (session history fails to display) — wait for the official stable release before upgrading the host.
Links
More in this category
Tencent/WeKnora#dsh-weknora★ 33047
Four read-only tools over a WeKnora knowledge base: list knowledge bases, hybrid passage search, reassemble one document's chunks in order, and WeKnora's own cited RAG or ReAct-agent answer with a resumable session id.
superdesigndev/treg★ 4972
Tool catalog for agents: search ~2,600 external endpoints (SEO and SERP, backlinks, social, people and company enrichment, ad libraries, scraping) by the task you want done, read each one's parameters and per-call price, then call it with the credential injected server-side. Ships the skill plus an MCP row that stays disabled until TREG_TOKEN is set.
TencentCloudBase/CloudBase-AI-Toolkit#dsh-plugin★ 1136
Tencent CloudBase backend for DeepSeek Harness — scaffold and deploy full-stack apps from chat, render query results as table cards with paging, sorting and CSV export, preview a deployment on its domain, and call the CloudBase MCP toolset (`mcp__cloudbase__*`) with device-code login.
gitroomhq/postiz-agent#dsh-postiz★ 509
Connects DeepSeek Harness to Postiz over MCP: list connected social media channels, fetch per-platform posting rules, and schedule, draft, or publish posts to X, LinkedIn, Instagram, Facebook, Threads, TikTok, YouTube, Reddit, Bluesky, Mastodon, Discord, Slack, Telegram and more; adds a postiz workflow skill.
EthanYoQ/Invoice-Downloader#dsh-invoice-downloader★ 502
Local IMAP invoice download, OCR, archive, and Excel reimbursement summaries for DeepSeek Harness.
anysearch-team/anysearch-dsh★ 457
AnySearch-powered real-time web and vertical search provider for DeepSeek Harness.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.