Skills
dsh-web-restart-jifeng15
jifeng15/dsh-web-restart/hot-plugin · ★ 0
Hot plugin lifecycle for DeepSeek Harness web: install/uninstall/update/enable-disable bundles without restart. Host plugin for the dsh-web-restart skill.
Manifest details
安装
dsh plugin --profile web add 'github:jifeng15/dsh-web-restart#5801eab3c3a170296d2c1e00f9562c09ec762bcc&path:/hot-plugin'GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试
For install and environment setup, plus plugin development tutorials, see DSH community docs ↗
README
Table of contents
- The problem it solves (what we actually hit)
- Quick Start
- Two Usage Scenarios (both supported)
- Scenario A: Via conversation (recommended) — you type nothing
- Scenario B: Manual command line — full control
- At a Glance: What It Can / Can't Do
- Commands
- Watchdog: fully automatic takeover (optional, default OFF)
- Conventions & Boundaries
- How It Works (30-second version)
- Architecture & Data Flow (technical)
- Components
- Hot install (no restart) data flow
- Safe restart data flow
- Single-source principle (why plugins must not double-mount)
- Environment dependencies
- Pitfalls We Hit
- Known issues / optional tweaks
- License
- Changelog
- v2.0.11 (fix: bare-hosting mis-kill when launched from ~/bin/dsh-web)
- v2.0.10 (bare-hosting auto-repair)
- v2.0.9 (double-launch self-heal — resume suspended instances)
- v2.0.8 (quit — fully stop, nothing stays in the background)
- v2.0.7 (coexists with dsh-market on pnpm-workspace.yaml)
- v2.0.6 (install.sh auto PATH)
- v2.0.5 (launchd watchdog — fully automatic takeover)
- v2.0.4 (migration race fix)
- v2.0.3 (remove single-source guard)
- v2.0.2 (cross-source single-source hardening)
- v2.0.1 (repair & health-check)
- v2.0.0 (hot install, no restart)
- v1.0.0 (safe restart)
True hot-loading for dsh web: after installing plugins, editing config, or upgrading dsh itself, see the effect immediately — no more manually restarting from the command line.
English | 简体中文
The problem it solves (what we actually hit)
While using dsh web, I found that DSH has three kinds of changes that require a process restart to take effect — installing/removing/updating plugins (bundle layers are composed at startup), editing profile config (cordis.patch.yml), and upgrading the dsh package itself.
Every time, I had to leave the conversation, go to the command line, re-type dsh web, and refresh the page to see the effect:
- ❌ Not timely: can't see plugin effects right after installing
- ❌ Manual: even when an agent installed the plugin in conversation, I still had to open a terminal
- ❌ Fragile: killing the process directly also kills the running agent session ("many rounds never finished")
This skill syncs "hot reload" and "hot restart" — things that DSH already hot-reloads (skills, AGENTS.md, settings) keep hot-reloading; plugin installs now hot-apply without a restart, and everything that genuinely must restart (config edits, dsh upgrade) is restarted safely and automatically by the agent. dsh web becomes truly hot-loadable: install a plugin in conversation, it's active immediately — no more manual restarts.
Quick Start
Three ways to install, pick any:
① Install via conversation (recommended) — say this in a DSH conversation (include the repo URL so the agent knows where to install from):
The agent installs and loads the skill automatically (skills are hot-loaded — ready immediately, no restart needed).
② One-line install — in your own terminal:
npx -y skills add https://github.com/jifeng15/dsh-web-restart -g -y -a universal --copy
npx skills addinstalls the skill files only (agents use the skill's script path — no command needed). To get thedsh-webcommand in your terminal, run once:bash ~/.agents/skills/dsh-web-restart/install.sh --bin-only(it installs to~/bin/dsh-weband auto-adds~/binto your shell PATH).
③ Clone & manual install — if you want to inspect the source first:
git clone https://github.com/jifeng15/dsh-web-restart.git && cd dsh-web-restart
bash install.sh
Ready after install (see "Two Usage Scenarios" below).
Two Usage Scenarios (both supported)
Scenario A: Via conversation (recommended) — you type nothing
After installing this skill, say in conversation "install plugin XX for me" (XX = any other plugin, e.g. dsh-market) or "upgrade dsh for me". The agent will:
- Load this skill automatically
- Install the plugin / upgrade dsh
- Automatically call
dsh-web installfor plugins (hot install, no restart) ordsh-web upgradefor dsh itself — the agent calls the script directly, you never touch the command line - For hot installs, the plugin is active immediately; for upgrades, you just refresh the page
For: users who manage plugins through DSH conversations. This is the skill's primary scenario — it's designed for agents.
First use note: the very first time you hot-install, the agent automatically installs the hot-install component and restarts once (so it can load) — you just refresh that once. After that, every plugin install is no-restart.
Scenario B: Manual command line — full control
If you prefer the terminal, install/remove plugins with the hot commands (no restart), and use restart for everything else:
dsh-web install <spec> # Install plugin: hot install (no restart) if available, else safe restart
dsh-web remove <pkg> # Remove plugin: hot uninstall if available, else safe restart (refuses CLI-managed — use `dsh plugin --profile web remove`)
dsh-web restart # Fallback/other: safe restart after config edits, dsh upgrade, or when hot apply is unavailable
dsh-web session # Report the resolved tmux session (auto-discovered)
dsh-web status # Check status anytime
Hot first, safe restart as fallback:
install/removetry the bundled dsh-web-hot (no restart, PID unchanged). If hot apply is unavailable (plugin not loaded, module-level code update, or the change can't be hot-applied), they fall back to a safe restart automatically.restartremains the unified command for config edits, dsh upgrade, and migration.Why the "extra step" is unavoidable: the skill's automation trigger is the agent — when you install via conversation, the agent is present and auto-invokes it; but when you install from your own terminal, no agent is involved, so you must run it manually once. This one-time step is worth it: it migrates dsh web into tmux hosting, and after that everything (conversation-driven) is fully automatic.
For: CLI users, script automation, and agent-internal calls. All commands automatically handle tmux hosting, port discovery, and session discovery — no preparation needed.
At a Glance: What It Can / Can't Do
| ✅ Can | ❌ Can't |
|---|---|
| Hot install/uninstall plugins via bundled dsh-web-hot — no restart (falls back to safe restart if unavailable) | Agent continuing seamlessly after restart — restart briefly disconnects, you refresh (structural limit) |
| Auto-restart dsh web after plugin changes, config edits, or dsh upgrade — no manual commands (conversation) | Hot-apply module code updates (Node require cache — must restart) |
reload takes effect after editing profile config (cordis.patch.yml) |
Auto-recover after reboot (you re-run dsh-web start once) |
| No tmux? Auto-installs it (macOS/Linux package managers) | Auto-upgrade non-npm/pnpm dsh installs (only hints) |
dsh web running in a plain terminal? Auto-migrates into tmux (run dsh-web start/restart, or enable the watchdog for fully automatic takeover) |
Skill additions, AGENTS.md edits, etc. (these already hot-reload; not needed here) |
Session not named dsh-web? Auto-discovers the hosting session |
Crash auto-restart is a separate opt-in (run-loop) |
Port not 3080? Auto-discovers (incl. --port 0 random ports) |
— |
| All terminals closed — dsh web keeps running, restartable anytime | — |
In one line: install/uninstall plugins without restarting (hot), and for everything that genuinely must restart (config edits, dsh upgrade, migration), it restarts safely and keeps dsh web resident — you just refresh after the brief disconnect.
Commands
dsh-web install <spec> # ★ Install plugin: hot install (no restart) if available, else safe restart
dsh-web remove <pkg> # ★ Remove plugin: hot uninstall if available, else safe restart (refuses CLI-managed — use `dsh plugin --profile web remove`)
dsh-web restart # ★ Fallback/other: unified safe restart after plugin changes, config edits, or dsh upgrade
dsh-web reload # Apply profile-config edits (cordis.patch.yml) via restart (= restart)
dsh-web upgrade # Upgrade the dsh package itself, then auto-restart
dsh-web start # Start / auto-takeover (create session if none, migrate into tmux if unmanaged)
dsh-web stop # Stop (Ctrl-C; tmux hosting stays, `restart` brings it back)
dsh-web quit # Quit fully: stop + close the tmux hosting session + clean up (nothing in the background)
dsh-web heal # Fix bare hosting (run-loop lost): re-host under run-loop (= one safe restart, brief disconnect)
dsh-web status # Check session/port/PID/logs
dsh-web session # Report the resolved tmux session (auto-discovered, never assumes `dsh-web`)
dsh-web attach # Enter tmux for troubleshooting
dsh-web autostart-on # Enable autostart at login (default OFF, user opt-in; launchd/systemd)
dsh-web autostart-off # Disable autostart
dsh-web autostart-status # Check autostart status
dsh-web watchdog-on # Enable launchd watchdog: every 30s, auto-migrate any unmanaged dsh web into tmux (default OFF)
dsh-web watchdog-off # Disable watchdog
dsh-web watchdog-status # Check watchdog status
dsh-web repair # Fix config: same-file duplicate ids, ghost bundles, cross-source duplicates (auto-backup first)
dsh-web health-check # Check port + config tree — is it a process or a config problem?
dsh-web preflight # Boot self-check (auto-run by start/restart): clear cross-source duplicates
# internal: report-port (write actual port to last-port.txt + notify non-default), watchdog-tick (launchd calls)
Hot install first, safe restart as fallback:
dsh-web install/removetry the bundled hot-plugin (dsh-web-hot) first — installing/uninstalling plugins without a restart. If hot apply is unavailable (plugin not loaded, or the change can't be hot-applied), it falls back to a safe restart automatically.dsh-web restartremains the unified command for everything else (config edits, dsh upgrade, migration).One owner per plugin (single-source): a plugin is mounted from exactly one source — either
dsh plugin --profile web add|remove(thedsh.profile.bundleslist) or hot install (thecordis.patch.ymlpatch layer), never both. If the same plugin ends up in both (e.g. an externaldsh plugin addadopts a previously hot-installed one),preflight(auto-run bystart/restart) andrepairdetect it and drop the patch-layer rows automatically — the next boot can't crash withduplicate loader entry id. On the remove side,dsh-web removerefuses CLI-managed plugins (it would leave a ghost bundle) and points todsh plugin remove.
Autostart is optional and OFF by default — the skill never enables autostart on its own. Run
dsh-web autostart-onwhen you want dsh web to start in tmux after login.
Port notification policy: after
start/restart, the actual port is written to~/.dsh/logs/last-port.txt. If the port is the default (3080), no notification (everyone knows it); if non-default (occupied/random), a system notification shows the actual port — especially useful for unattended autostart.
Watchdog: fully automatic takeover (optional, default OFF)
dsh-web watchdog-on installs a launchd LaunchAgent that checks every 30 s:
if dsh web is listening but not hosted in tmux (e.g. you started it in a
plain terminal), it automatically migrates it into tmux — so after that,
closing the terminal never kills dsh web, no matter how you started it.
The watchdog only migrates a running web; it never starts a stopped one.
Disable with dsh-web watchdog-off. macOS only (Linux systemd timer: future).
Conventions & Boundaries
| Item | Default | Override |
|---|---|---|
| tmux session name | dsh-web (auto-discovered if not found) |
DSH_WEB_SESSION |
| Port | auto-discovered (explicit config > process --port > process listen port > node scan > default 3080) |
DSH_WEB_PORT |
| Launch command | dsh web |
DSH_CMD |
- Port auto-discovery: works even if you launched dsh web with
--port 8080or--port 0(random) — the script parses the actual port from the process command line or listening socket. - Closing all terminals is fine: tmux server is a daemon, detached sessions keep running, auto-restart still works.
- After reboot /
tmux kill-server: reopen dsh web however you like —dsh-web startis easiest (creates tmux + starts + hosts in one step); plaindsh webalso works, firstrestartauto-migrates into tmux (one extra migration, then fully automatic). - If tmux can't be auto-installed, platform-specific manual commands are printed.
How It Works (30-second version)
tmux run-shell -b "sleep 3; tmux send-keys -t dsh-web C-c; sleep 2; tmux send-keys -t dsh-web 'dsh web' Enter"
tmux server is an independent daemon — it doesn't depend on dsh web being alive. Even if the caller (agent) dies with dsh web, the restart still completes.
Architecture & Data Flow (technical)
Components
| Component | Role |
|---|---|
scripts/dsh-web.sh |
Main CLI (start/restart/install/remove/upgrade/status/session/autostart-*) |
hot-plugin/ (dsh-web-hot) |
Host plugin: hot install/uninstall via include.update (no restart) |
scripts/run-loop.sh |
Crash auto-restart loop (3-strike circuit breaker) |
scripts/install-tmux.sh |
Cross-platform tmux auto-install |
install.sh |
One-shot install (skill + CLI + hot-plugin + tmux) |
Hot install (no restart) data flow
dsh-web.sh install <spec>
→ POST /dsh-web-hot/install {spec}
→ pnpm add <spec> (profile dir, official registry)
→ read bundle's dsh.bundle.patch → patch rows
→ write cordis.patch.yml (user patch layer, persistent)
→ include.update (hot apply, PID unchanged)
→ record dsh-web-hot.state.json
→ {"ok": true}
Safe restart data flow
dsh-web.sh restart
→ resolve_session (discover actual session, e.g. "0")
→ tmux run-shell -b "sleep 3; C-c; sleep 2; 'dsh web'"
→ tmux server executes independently (completes even if agent dies)
→ 5-8s later dsh web restarts; user refreshes
Single-source principle (why plugins must not double-mount)
dsh plugin --profile web add (the bundle list) and hot install (the user patch
layer) both feed include entries into the same loader, whose entry ids must be
unique. If the same plugin ends up in both sources, the next boot dies with
duplicate loader entry id. This project enforces one owner per plugin:
- hot install refuses bundles already in
dsh.profile.bundles(install-time guard); preflight(auto-run bystart/restart) andrepairdetect cross-source duplicates — entry ids declared by bundles ∩ patch-layer rows — and drop the patch-layer rows (bundles side is authoritative), syncingdsh-web-hot.state.json;- the
dsh-web-hotplugin self-heals at startup for running-process drift (HMR reloads, manual state edits), hot-unmounting rows viainclude.update.
So an external dsh plugin add that adopts a previously hot-installed plugin is
auto-reconciled before the next boot — no manual cleanup needed.
Environment dependencies
| Dep | Use | If missing |
|---|---|---|
| tmux | hosting + independent restart | auto-installed |
| pnpm | plugin install (via dsh-web-hot) | hot install degrades to safe restart |
| dsh CLI | install.sh hot-plugin install | hot-plugin skipped |
| curl/lsof/ps/pgrep | probing | — |
Pitfalls We Hit
- Synchronous restart = kills the host process = command interrupted → use
tmux run-shell -b nohup ... &background tasks get cleaned up when the caller's turn ends → use tmux server- GitHub tarball URL plugin installs leave pnpm lockfile missing
integrity→ usegithub:owner/repo#ref - Running
dsh webagain while it's already hosted — the second instance can't bind the port (EADDRINUSE), and the hosted one can end up suspended (run-loop exits on the signal). Don't double-launch: usedsh-web start/restart/status. If it happened anyway: the watchdog auto-resumes suspended instances within 30s, anddsh-web restartrestores proper hosting. ⚠️ Before you runrestart: it briefly disconnects the web and interrupts any agent session running inside it — run it when convenient, or let the next session do it. Afterward verify withdsh-web statusthat run-loop is in the parent chain (crash auto-restart is back; otherwise the web is only hosted by a bare process). Bare hosting is auto-repaired: the watchdog detects it and fixes it back to run-loop hosting within 30s (also a brief disconnect);dsh-web healfixes it immediately.
Known issues / optional tweaks
pnpm 11's 24h
minimumReleaseAgesupply-chain gate: updating plugins published within the last 24h viadsh plugin/ dsh-market can warn or fail ("too-young release"). This project's hot install bypasses it per operation with--config.minimumReleaseAge=0, so it is unaffected; dsh-market retries once itself, but can still hit mirror sync lag (this machine's~/.npmrcpoints to npmmirror and dsh-market does not pin the official registry). To allow it globally (e.g. you often install freshly-published plugins), add to~/.dsh/profiles/web/pnpm-workspace.yaml:minimumReleaseAge: 0Trade-off: you give up the 24h gate (guards against freshly-published-abuse packages). Choose as you see fit; apply with
dsh-web reload.
License
MIT
Changelog
v2.0.11 (fix: bare-hosting mis-kill when launched from ~/bin/dsh-web)
- 🩹 Fix: starting via
~/bin/dsh-webused to degrade hosting to a "run-loop-less bare process" (early install.sh didn't ship run-loop.sh), so the watchdog misjudged it as bare hosting and killed it — the firststartcame up then the whole tmux server vanished; the second survived only because of the 5-min cooldown.crash_loop_cmdnow falls back to the skill copy'sscripts/for run-loop.sh;install.shshipsrun-loop.shto the bin dir too;auto_rehostskips when the hosting session no longer exists (safety).
v2.0.10 (bare-hosting auto-repair)
- 💊 The watchdog now detects bare hosting (web in the hosted session but run-loop lost — crash auto-restart gone) and auto-repairs it back to run-loop hosting (stop bare process → wait for port → relaunch under run-loop in the same session), with a 5-minute cooldown to avoid repeated disruptions. The repair is one safe restart (the agent session is interrupted once).
- ✨ New
dsh-web heal: trigger the same repair manually (no cooldown). - ✅ Verified in isolation: bare hosting auto-repaired (pane relaunched via the run-loop path), proper hosting untouched, heal command chain works.
v2.0.9 (double-launch self-heal — resume suspended instances)
- 💊 The watchdog now resumes suspended dsh web processes (SIGCONT) on every
tick. If you accidentally run
dsh webagain while it's already tmux-hosted, the new instance fails on the busy port and the hosted one can end up suspended — the watchdog brings it back within 30s. Recovery guidance:dsh-web restartrestores proper run-loop hosting.
v2.0.8 (quit — fully stop, nothing stays in the background)
- 🚪
dsh-web quit: stops dsh web (Ctrl-C, TERM fallback), closes the tmux hosting session and cleans up (crash-count) — nothing stays in the background.stoponly sends Ctrl-C and keeps the hosting session (sorestartis fast);quitis the "I'm done" exit. If the watchdog is active it prints a reminder (it never auto-starts a stopped web).
v2.0.7 (coexists with dsh-market on pnpm-workspace.yaml)
- 🔀
ensureWorkspaceAllowednow merges into dsh-market'sallowBuildsobject style (name → boolean, incl. its "set this to true or false" template) instead of overwriting it with our legacy list format. Both tools can now write the same file without clobbering each other.
v2.0.6 (install.sh auto PATH)
- 🔧
install.shinstalls thedsh-webcommand and auto-adds its bin dir to your shell PATH (previously the command was installed but not found).
v2.0.5 (launchd watchdog — fully automatic takeover)
- 🐕
dsh-web watchdog-on: a launchd LaunchAgent checks every 30 s — if dsh web is listening but not hosted in tmux (e.g. you started it in a plain terminal), it auto-migrates it into tmux. So no matter how you start dsh web, closing the terminal no longer kills it. Opt-in (default OFF), managed withwatchdog-status/watchdog-off. The watchdog only migrates a running web — it never starts a stopped one.
v2.0.4 (migration race fix)
- 🔧
startauto-takeover (plain-terminal dsh web → tmux) now creates an empty tmux session first and launches dsh web only after the old process is stopped and the port is free. Previously the new instance started immediately and fought the old one for the port (EADDRINUSE), burning the run-loop's 3-strike breaker. - ✅ Verified end-to-end with a dummy process + harmless launch command: empty session → old process killed → port released → dsh web starts inside tmux.
v2.0.3 (remove single-source guard)
- 🔒
dsh-web remove <pkg>now refuses plugins managed bydsh plugin(present indsh.profile.bundles) and points you todsh plugin --profile web remove <pkg>. The old fallback removed the dependency but left the bundle entry behind (ghost bundle). One owner per plugin, now enforced on the remove path too.
v2.0.2 (cross-source single-source hardening)
- 🛡️ Cross-source duplicate detection & self-heal — the
duplicate loader entry idcrash (same plugin mounted via bothdsh.profile.bundlesand the hot-install patch layer) is now prevented and auto-repaired:dsh-web repairruns a new cross-source check first: entry ids declared by bundles ∩ patch-layer rows → drops the patch-layer duplicates (bundles side is authoritative) and syncsdsh-web-hot.state.json.dsh-web preflight(boot self-check) —start/restartrun it before launching, so an external takeover (e.g.dsh plugin addadopting a hot-installed plugin) can never crash the next boot.dsh-web-hotstartup self-heal — the host plugin drops patch rows for any hot-managed bundle thatdsh.profile.bundleshas since adopted (covers running-process drift / HMR reloads).
- 🩹 v2.0.1's
repair/health-check/ auto config backup are now documented here too (they shipped in v2.0.1 but the README wasn't synced).
v2.0.1 (repair & health-check)
- 🛠️
dsh-web repair: dedup duplicate ids, remove ghost bundles, resyncdsh-web-hot.state.json; auto-backup before any config change (keeps last 10). - 🩺
dsh-web health-check: port readiness + config tree (--dump-config) — tells you whether it's a process or a config problem. - 🔒 Hot install rejects bundles already in
dsh.profile.bundles(prevents the duplicate loader entry id crash from the hot-install side).
v2.0.0 (hot install, no restart)
- 🎉 Hot install/uninstall — installing/removing plugins no longer restarts.
Bundled
dsh-web-hothost plugin hot-applies patch rows viainclude.updateat runtime; the PID never changes. - ✨ New commands:
dsh-web install <spec>(hot first, fallback to safe restart),dsh-web remove <pkg>(hot first, fallback to safe restart). - 🛡️
dsh-web session: report the resolved tmux session (auto-discovered, never assumesdsh-web). - 📦 Module-level code updates still require a restart (Node require cache) — structural limit, falls back automatically.
v1.0.0 (safe restart)
- First release: tmux hosting + tmux-server independent delayed restart, covering plugin changes, config edits, and dsh upgrades.
- Crash auto-restart with a 3-strike circuit breaker; port/session auto-discovery; bilingual README.