Checks npm hourly for newer versions of the installed plugins and asks which ones to upgrade.
Install
# from npm (prebuilt)
dsh plugin --profile web add dsh-update-notifier
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:stuarthu/dsh-update-notifier
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. Only install sources you trust, and pin a commit (github:owner/repo#sha).
README
English | 中文
Hourly — or on demand, with /check-updates — check for newer versions of your
installed DeepSeek Harness
(dsh) plugins, with a one-click approval bubble to upgrade them.
Nothing in dsh tells you a plugin you installed has moved on; you find out by
remembering to run npm view yourself. This plugin closes that gap: it compares
every installed plugin against the registry once an hour and, when something is
newer, asks you — in the session, as a real question bubble — which ones to
upgrade. Tick the ones you want and the agent runs the upgrade in front of you.
It never upgrades anything on its own.
Behavior
Once an hour (configurable), for every npm package backing a loaded plugin:
Look up the registry's
latestand compare it with the version installed in your profile'snode_modules. Comparison is semver-proper — prereleases ordered correctly, downgrades never suggested — and a version that isn't a strictX.Y.Zis skipped rather than guessed at.Ask, in one bubble. Anything newer becomes a multi-select question rendered by dsh itself (
dsh-chrome 0.1.2 -> 0.1.3, one option per plugin), attached to your most recently active session. It costs no model turn and no tokens, and it is replayed if you reconnect.Hand the ticked ones to the agent, as a follow-up message telling it to run
pnpm --dir <profileDir> add <pkg>@<latest> ...The agent wakes immediately and you watch the upgrade happen. Pair this with
dsh-hot-reloadand the new version applies live, without restarting dsh.
/check-updates
Don't want to wait for the hour to come round? Type /check-updates in the
session:
/check-updates
check-updates · 2 updates — see the question above (7 plugins checked)
It runs a full cycle immediately, against the session you typed it in, and the
row settles as soon as the registry sweep finishes — the bubble it raises is an
ordinary one, answerable whenever, surviving a refresh. Where no bubble can be
shown at all, the settlement carries the pnpm ... add command instead, so the
answer is never just "you can't".
Packages the registry could not be reached about — a network failure, a timeout,
a 5xx, a rate-limit, or an answer with no version in it — are counted in the
settlement rather than passed off as up to date, and a sweep where nothing
could be reached settles as an error, not as "no updates". The two counts add up:
5 plugins checked, 2 unreachable means seven were swept. Cancelling the row stops the sweep, so a check you cancel cannot
pop a bubble at you a minute later.
The manual check ignores the decline memory below: you asked, so you get the full list, including versions you left unticked earlier. It does not disturb the hourly schedule, and it declines to run — with a reason — while a bubble is already open or a check is already in flight.
The command appears wherever dsh composes a command registry — which
@deepseek-ai/dsh-base does, so any profile built on it has /check-updates.
It is registered through ctx.inject, so a headless profile without one still
loads the plugin and keeps its hourly checks.
Asked once
You are asked about a given version once (by the hourly cycle — the command above overrides this):
- Unticked ones are declines, recorded in
<profileDir>/.dsh-update-notifier.jsonand never offered again — across restarts — until the registry'slatestchanges to some other version that still beats what you have installed. Delete that file to be asked afresh. - Approvals are not recorded: a successful upgrade makes that version current anyway, and one that failed is worth re-offering after a restart.
- A bubble you never answered (session ended, dsh stopped) is not a decline — you will be asked again.
With no live session yet, the offer is held: it is retried right after your next
message and on the next hourly cycle. It is also held where nothing can render
the bubble at all — a headless profile, or one that composes the userQuestions
service with no UI registered against it — and there the available updates and
the exact upgrade command go to the log as well, because no message of yours is
going to make a bubble appear.
Install
dsh plugin --profile web add dsh-update-notifier
Then restart dsh once (bundle patch layers load at boot). Works in any
profile — swap web for whichever profile you use; it checks the profile it
is loaded into. Needs Node 18+, for global fetch; any dsh host has it.
Compatibility
Built and tested against dsh 0.1.0-rc.6 (Node 22 / 24). It uses only
public host services, so it degrades rather than breaks if one is absent:
| Host surface | Used for |
|---|---|
loader.entries() |
finding which packages back the loaded plugins |
userQuestions.ask() |
rendering the approval bubble |
userQuestions.provider |
not promising a bubble no UI has registered to render |
agents.get(sessionId) |
resolving the session's live agent |
agent.followup() |
waking that agent to run the upgrade |
session/event (user/message) |
tracking the most recently active session |
agent/disposed |
withdrawing a bubble whose session died |
commands.register() |
the /check-updates command |
agents.roots() |
not promising a bubble ask() would refuse |
userQuestions and commands are resolved when needed rather than declared as
dependencies, so the plugin still loads in a headless profile that has neither.
Opting out
A plugin that does not want to be offered for upgrade can say so in its own
package.json:
{ "dsh": { "updateNotifier": false } }
Use exclude (below) to silence a plugin you do not control.
Configuration
Set on the update-notifier row in your profile's cordis.patch.yml:
| Key | Default | Meaning |
|---|---|---|
interval |
3600000 |
ms between checks (clamped to a 60000 minimum) |
initialDelay |
10000 |
ms before the first check after boot |
registry |
https://registry.npmjs.org |
npm-compatible registry to query |
exclude |
[] |
package names to never check or mention |
fetchTimeout |
10000 |
per-request registry timeout, ms (minimum 1000) |
profileDir |
auto | absolute path to the profile dir (auto-detected from the loader base URL if omitted) |
Security
- Nothing is upgraded without your click. This plugin never spawns a package manager. It composes the bubble and, on approval, the instruction the agent then executes in plain sight.
- No registry prose reaches the UI or the model. Only the package name, read
from your own profile, and a version string that passes
/^[0-9A-Za-z.+-]{1,64}$/are ever put into the bubble or the agent message — never a description, README, or changelog fetched from the registry. - The follow-up message carries
source.kind: "plugin", names exactly the approved packages, and tells the agent not to touch anything else.
Limitations — read this
- It asks; it does not verify. Once you approve, the upgrade is the agent's to run. A failure is visible in the session, but this plugin neither retries it nor checks afterwards that the new version is really installed.
- Only registry-installed plugins are seen. A plugin linked from a local
checkout, or built into dsh, has no package under the profile's
node_modulesand is skipped silently — no bubble, no log line. - Only the
latestdist-tag is consulted. There is no support for pinned ranges,next/betachannels, or holding a package back at a major version; a plugin whose newest release you never want should useexclude. - One bubble at a time. While a bubble waits for an answer the hourly checks
skip — and
/check-updatesrefuses with "an upgrade question is already open" — so anything published meanwhile is picked up on the first cycle after you answer. - A decline is keyed to that exact version, not to "anything up to it": if
the registry's
latestmoves to a different version that still beats yours — including a lower one after an unpublish — you are asked again. - The bubble needs a session that dsh can attach a question to. Registry failures, degraded loader state, and an unwritable state file all degrade to logging; only two things disable the plugin outright — no plugin loader on the context, and a profile dir it cannot locate — and it says which in the log.
License
MIT © Stuart Hu
Links
More in this category
omdsh-dev/dsh-notification★ 55
Desktop notifications for turn completions, with per-outcome controls and keyword rules.
omdsh-dev/dsh-open-in-vscode★ 47
Open DSH workspace directories in VS Code directly from the web GUI.
whyihaveyou/dsh-suite#plugin-notify★ 38
IM webhook and local notifications on turn completion, errors, or approval (Feishu/WeCom/DingTalk/Slack/Discord/custom).
THEWOLFWALKER/dsh-notifier★ 32
Unified notification & remote control for DSH: one `notify()` API, 25+ channels (Telegram / DingTalk / Feishu / WeCom / QQ bot / WxPusher / PushPlus / ServerChan / Bark / Discord / Slack / ntfy / webhook...), level routing (timeSensitive / active / passive) with tiered retry, multi-channel inbound approval (Telegram buttons, Feishu cards, QQ, WxPusher, WeChat iLink), official QR login for QQ/DingTalk/Feishu, a local web admin console, multi-agent routing, desktop notifications — and a mobile command center: `!status` / `!stop` / `!retry` agent control from your phone plus actionable notifications (view result / retry / logs buttons that call back into the agent). Secrets redacted, tool rate-limited, zero runtime deps — plus an open event source: other plugins can inject the notifier service (ctx.notifier) and subscribe to dsh-notifier/sent events, reusing notification without coupling — and an identity system (v0.7): pairing codes (/pair in any DM, first redeemer becomes owner), composite-key bindings, member roles, and a guided bootstrap state when the whitelist is empty.
omdsh-dev/dsh-lark★ 24
Lark/Feishu bot channel for DeepSeek Harness: each chat drives its own agent, and tool approvals, model questions, and plan reviews return as cards answered by a button or a reply. Switch workspace and model from the chat (`/cd`, `/model`, `/new`), and run several bots that keep separate sessions and can hand turns to each other in one group.
xmanrui/dsh-im★ 23
Connect IM bots to DeepSeek Harness by scanning QR codes (supports Feishu, Weixin, DingTalk, and more).