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:
- 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.”
- Type the
/restartcommand 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, orsec-fetch-siteiscross-siteor the Origin is from a different source, return403;X-Forwarded-Foris 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:
- Click the sidebar restart button (or type
/restart) to complete the secondary confirmation. - Frontend
POSTto/api/restart-dshand receives202. - About 3 seconds later, the service restarts; the page briefly shows “Restarting…” and then automatically reconnects.
- If there were running agents before the restart, journald will show
recovery armed for N session(s)andresuming 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