DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

Neptune810 /

Neptune810/dsh-power-button

Verified

A draggable one-press stop button for the DeepSeek Harness Web GUI. / DSH 一键结束服务按钮。

★ 0 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@d30c9a68

dsh-power-button

A DeepSeek Harness Web plugin: one draggable power pill in the GUI, with a live status dot and restart / stop actions for the running dsh web service.

Host-only, zero dependencies, no build step.

Why

dsh web keeps running until something stops it. A deployment that wraps it in a watchdog usually waits out an idle window before tearing the server down, so the port stays occupied for a while after the last browser tab closes, and a relaunch in that window re-opens a page whose server is about to disappear.

This plugin puts both actions where the user already is — one pill in the Web GUI, with a dot that says whether the host is answering.

Use

A small translucent pill sits in the configured corner (bottom-right by default).

  • The dot — green when the host answered the last liveness poll, red when it did not. Polling is a tiny same-origin GET every 5 seconds and costs no model tokens.
  • Press the pill — a small menu opens with two actions:
    • 重启服务 (Restart) — the host relaunches itself in place and a new page opens automatically once the port answers again.
    • 结束服务 (Stop) — the process ends immediately and the port is released.
    • Pressing the pill again, or clicking anywhere else, closes the menu.
    • Set restart: false and a press stops outright, with no menu at all.
  • Drag the pill — move it anywhere, with mouse or touch. The position is stored in localStorage and restored on the next load.
  • Dragging never fires an action: pointer travel past 4px marks a drag, and the click the browser emits afterwards is swallowed.
  • The pill is clamped inside the viewport — and re-clamped when the window shrinks — so it is always reachable.
  • It lives outside the application's React root, so a re-render cannot remove it.

How it works

The plugin is host-only. No client bundle is built and no @deepseek-ai/* package is imported: a plugin installed with link: is resolved by Node to its real path, where the harness packages are not reachable. It uses two extension points the Web carrier already exposes:

Extension point Used for
webServer.tapIndex() injecting the pill's markup, CSS, and script into index.html
webServer.register() serving /dsh-power-button/status, /restart, and /stop

Stopping calls the launcher's ctx.appExit — the same bounded shutdown path Ctrl+C uses: dispose the application tree, then let the process exit. That path only ends the process if the event loop actually drains, so the plugin also arms an unreferenced 2-second watchdog: it never delays a clean exit, and it guarantees the process is gone even when another plugin is still holding a handle.

Restarting cannot be done by the host, because the host is the thing going away. The restart route spawns a detached helper — the same Node binary running an inline module — which

  1. waits for this process to disappear,
  2. waits for the port to stop answering,
  3. starts the recorded boot invocation again with the same environment and working directory,
  4. and, when asked, opens the page as soon as that port answers.

The helper outlives the host (detached + stdio: "ignore" + unref()), so the relaunch survives the shutdown that triggered it.

Measured on Windows 11 / Node 24: a stop releases the port ~0.2–0.5 s after the POST returns 200; a restart brings the port back in a few seconds, and the failure mode is safe — if the helper cannot be spawned, the route answers 500 and the service is left running.

Security

All three routes require every one of:

  1. the custom request header x-dsh-power-button: stop — a cross-origin page cannot send it without a CORS preflight this server never approves, so no random website can restart or stop your server;
  2. a loopback Host (127.0.0.1 / localhost / [::1]), which closes the DNS-rebinding variant;
  3. an Origin, when the request carries one, matching that Host.

Anything else is 403. status answers only GET/HEAD; stop and restart answer only POST — a non-matching method is 405.

Nothing outside the profile is touched: the restart helper runs the same boot invocation this process was started with, or exactly the restartCommand you configured.

Config

Every key is optional. Set them on the plugin row:

`@yaml

  • insert:
    • id: power-button name: dsh-power-button config: label: 结束服务 # pill text and tooltip position: bottom-right # starting corner, or bottom-left draggable: true # false pins it to that corner status: true # false removes the dot and stops polling statusPollMs: 5000 # liveness poll interval, minimum 1000 restart: true # false removes the restart action and the menu restartCommand: [] # argv; empty = resume this same boot invocation restartOpenBrowser: true # false when the command opens the page itself restartWaitMs: 30000 # how long the helper waits for the port before giving up enabled: true # false injects nothing

`@

Restarting through a launcher

By default the helper resumes this invocation — the same Node binary, script, and arguments. That is the only thing the plugin can know for certain, and it means a relaunched host has no wrapper watching it: it stays up until you stop it.

If your deployment is started by a launcher that must come back too — for instance one with its own idle watchdog — point restartCommand at it and let it own the browser:

`@yaml

  • insert:
    • id: power-button name: dsh-power-button config: restartCommand: - 'C:\path\to\YourLauncher.exe' restartOpenBrowser: false

`@

Install

@sh dsh plugin --profile web add dsh-power-button @

Restart dsh web afterwards: a plugin change never reaches an already-running process.

Test

@sh node --test test/plugin.test.js @

No test dependencies. The injected page script is executed for real against a hand-built DOM — press versus drag, the movement threshold, clamping, persistence, the menu, the restart action, and the status poll — and the routes are driven through fake node:http request/response pairs, including the compression path that silently drops res.end callbacks. The detached helper's source is syntax-checked as ESM so a broken relaunch cannot ship.

License

MIT

—/ 5

No ratings yet

Verified DSH bundle

Commit d30c9a688a31

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout