Introduction

If you run dsh-web using systemctl --user, you’ve probably gone through this routine: open a terminal, type systemctl --user restart dsh-web, and then go back to the browser to manually refresh the page. If there’s an agent task running, that turn gets interrupted, and you have to manually add a sentence to make it continue.

dsh-restart-systemd brings this into the WebUI: click the restart button in the sidebar (or type the /restart command), the service restarts in about 3 seconds, the browser reconnects automatically, and the interrupted session resumes automatically. Below is an introduction to its implementation logic, installation methods, and usage boundaries.

What is it

dsh-restart-systemd is a DSH plugin, positioned as the “DSH WebUI Restart Button (systemd version)”, maintained by RoyougiShiki, licensed under MIT, current version 0.1.0.

It does only one thing: schedule systemctl --user restart dsh-web, and handle the cleanup neatly—browser auto-reconnect, interrupted agent sessions auto-resume. The main scenario is systemd user mode under WSL/Linux (tested), while the Windows native branch is a placeholder.

Core Implementation

Trigger and Scheduling

The plugin provides two trigger entry points, behaving consistently:

  1. The restart button in the sidebar footer, located next to the remote-webui phone icon. Upon clicking, a secondary confirmation pops up: “Ongoing agent tasks will be interrupted and auto-resumed; the page will automatically reconnect in a few seconds.”
  2. Type the /restart command in the chat, which returns confirmation text.

Upon confirmation, the frontend POST to /api/restart-dsh. On the host side, it first snapshots the currently running agent IDs into a resume list, writes a flag file, and immediately returns 202 {scheduled:true, delayMs:3000}. Then, 3 seconds later, it spawns systemctl --user restart dsh-web. The spawn uses a whitelisted argv list, without shell string concatenation. The 3-second delay is intended to let the browser receive the 202 first.

Anti-Abuse Protection

Restart is a destructive operation, and the plugin implements several layers of protection:

  • Single-Flight Lock: If one is already in flight, repeated POST requests return 409 already-scheduled.
  • Residual Window: Clicking again within 15 seconds after the plugin-driven boot (flag consumed) returns 429, preventing retry pings.
  • Loopback Fence: If socket/Host is not 127.0.0.1/::1, or sec-fetch-site is cross-site or the Origin is from a different source, return 403; X-Forwarded-For is never trusted.

The flag file $DSH_HOME/dsh-restart.flag acts as a one-time boot token: it is written before spawning and consumed once upon boot, then deleted; it never triggers a restart on its own. Therefore, a residual flag cannot cause a secondary restart loop.

Auto-Reconnect and Session Resume

After the service restarts, the browser automatically reconnects via ConnectionController with exponential backoff (500ms → 10s), without the need for manual refresh.

On the new boot, the plugin consumes the flag, installs an agent/created listener, and reads the resume list $DSH_HOME/dsh-restart-resume.json: for sessions whose last turn was interrupted (an unclosed turn/start exists, or the most recent turn/end.reason === 'interrupted'), it executes agent.followup("Continue.") to auto-resume. Normal sessions are never disturbed. Recovery is best-effort: agents not rebuilt within the 60-second RECOVERY_TIMEOUT_MS window are discarded.

Dual-Sided Plugin

This is a dual-sided plugin. A single line is injected via cordis.patch.yml, so after installation, both the host and client halves mount simultaneously:

Half Running Environment Responsibility
host node (host) /api/restart-dsh route, /restart command, flag/resume status
client browser Sidebar button UI, secondary confirmation, status feedback

The route only registers /api/restart-dsh, avoiding the /plugins prefix, preventing bundle number conflicts.

Installation and Enablement

First, build it. You need devDependencies:

cd dsh-restart-systemd
npm install
npm run build    # tsc -b → lib/

Install into the web profile:

dsh plugin --profile web add /tmp/dsh-restart-systemd

After installation, hot reload is disabled, so you need to restart the service for it to take effect:

systemctl --user restart dsh-web

Verify the plugin is loaded: Open a new session, journalctl --user -u dsh-web shows logs starting with dsh-restart-systemd:, and the sidebar footer row displays a restart icon.

Typical Usage

The complete process consists of four steps:

  1. Click the sidebar restart button (or type /restart) to complete the secondary confirmation.
  2. Frontend POST to /api/restart-dsh and receives 202.
  3. About 3 seconds later, the service restarts; the page briefly shows “Restarting…” and then automatically reconnects.
  4. If there were running agents before the restart, journald will show recovery armed for N session(s) and resuming interrupted agent <id>, and the corresponding sessions will continue automatically.

The entire process can be seen as three logs in journald in order:

restart scheduled  delayMs=3000
spawning restart for dsh-web consumed leftover restart flag

Verify the loopback fence by spoofing the Host header to send a cross-site request; the expected response is 403:

curl -H 'Host: evil.example' -X POST http://127.0.0.1:3080/api/restart-dsh
# 403 forbidden: loopback-only

For single-flight verification, quickly click the button twice; the second time returns 409 already-scheduled.

Rollback:

dsh plugin --profile web remove ui-dsh-restart-systemd
systemctl --user restart dsh-web
rm -f ~/.dsh/dsh-restart.flag ~/.dsh/dsh-restart-resume.json

After restart, the button and command disappear without changing other configurations; the two state files can be deleted at any time.

Applicable Scenarios and Notes

This plugin is suitable for developers whose main environment is WSL/Linux, who start dsh-web using systemctl --user, and who frequently need to restart the service to load plugins or change configuration.

Understand a few boundaries before use:

  • Single Service Unit: The systemctl command is fixed to dsh-web; there are no configuration options to change.
  • Doesn’t work if not started with systemctl: If you run dsh-web manually with node ..., spawning systemctl will fail and log an error, but no restart will occur.
  • Recovery is best-effort: Agents not rebuilt within the 60-second window are discarded; clean sessions are never actively resumed.
  • Windows is a placeholder: Native Windows (non-WSL) uses a detached helper branch, but the helper itself is not included with the package and is machine-specific; the main WSL scenario does not enter this branch.
  • Version 0.1.0: The homepage/bugs fields in package.json are still placeholders (<your-org>).

Finally, a general reminder: the plugin runs with the permissions of the current dsh process, and this plugin can trigger a service restart. Although it comes with a loopback fence, the plugin mechanism itself implies a significant degree of trust—check the source code and license before installing (this project is MIT).

Conclusion

dsh-restart-systemd does something small but restrained: it makes “restarting the service” no longer interrupt your workflow. Click a button, the browser reconnects automatically, interrupted sessions resume automatically, and anti-restart-loop protection is handled in the scheduling layer. If your dsh-web runs on systemd, it’s worth a try.

  • Directory page (Community-maintained independent site, no official affiliation with DeepSeek / Hypothesis): https://www.skillhub.cn/plugins/RoyougiShiki/dsh-restart-systemd
  • Source Code: https://github.com/RoyougiShiki/dsh-restart-systemd