DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

Sidebar Restart — DeepSeek Harness 插件(DSH Plugin)
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins
S

dsh-sidebar-restart

Sidebar Restart

在 DSH 侧边栏底部的 Settings 旁添加一个 Restart 行:桌面应用会在进程外重启,随后重新加载标签页,以确保实际加载新安装的插件包。

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add github:DONGBIN007/dsh-sidebar-restart#1867e6df330363e3d6f92db954d3038336ba45c8
README兼容性版本
The Restart row above Settings in the desktop appThe Restart row above Settings in the sidebar foot

兼容性与来源证明

Sidebar Restart 以 dsh-sidebar-restart 发布,当前版本为 0.1.0。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
web
发布来源
github
Registry 更新时间
2026/10/18

版本

0.1.0stable
2026/10/18

相关插件

正在加载相关插件…

最新版
0.1.0
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
未提供
文件数
未提供
Surface
web
许可证
MIT
发布源
github
GitHub
★ 0
周下载
0
最近提交
2026/10/18
查看源码 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题

相关插件

继续浏览 developer-tools 分类下经过校验的插件。

Web App@deepseek-ai/dsh-web-appdsh 浏览器界面捆绑包:位于 dsh-base 之上的 Web 补丁层,加上运行时粘合插件(提供前端 dist、Web 界面提示符、bash 运行时变量和 URL 行)Sdk Minimal@deepseek-ai/dsh-sdk-minimal独立的最小 SDK 配置包:JSON-RPC、一个 DeepSeek 适配器、持久化 Shell 和 JSONL 会话Sdk App@deepseek-ai/dsh-sdk-appdsh SDK 配置包:基于 dsh-base 提供 stdio JSON-RPC 服务和进程生命周期管理Experimental Terminal Bundle@deepseek-ai/dsh-experimental-terminal-bundle在多次工具调用之间保持 shell 进程运行。

README

dsh-sidebar-restart

One row in the sidebar foot, beside the settings gear: restarts the DSH desktop app in a single click, instead of quitting from the tray and relaunching by hand.

The Restart row above Settings in the desktop app

The same row in the mobile drawer, label included

三种形态

Install

dsh plugin --profile desktop add dsh-sidebar-restart   # once it is on npm

or straight from the repository:

dsh plugin --profile desktop add github:DONGBIN007/dsh-sidebar-restart

Then restart the app once by hand so the plugin loads — which is the last time you have to do that, since this button is what you use from then on.

Desktop only. On a plain dsh web profile the row renders disabled and says why; there is no desktop process to replace there. If you wanted the Web server restarted, that is a different plugin (dsh-restart-button).

What it does, in one line

Adds a Restart row to the desktop app's sidebar foot beside Settings. Clicking it asks the Host to restart; a detached helper kills the Electron main process and the Host by PID, waits until both are gone, starts the app again, and the tab reloads itself once the replacement answers with a new boot id — so a plugin you installed a moment ago is actually running afterwards.

The Restart row above Settings in the sidebar foot

What it looks like

Desktop app (wide sidebar)Mobile drawer
desktopmobile

Both follow the host's own rule rather than a breakpoint of mine: the label appears exactly where the host shows one for the gear, i.e. when the sidebar is wide. In the narrow 56px rail the row stays icon-only and keeps the gear's 36×36 target.

The control is a copy of the gear row next door

The seat is sidebar.footer.action — "optional actions beside Settings at the sidebar foot". The shipped sidebar renders it as footerActions, a flex row that footArea lays out above settingsArea (the gear row). So in the plain host footer this control sits directly above the gear.

It is deliberately styled as the gear's twin, with every value read from the shipped .wCInkW_trigger (ui-settings-general) and the seat's own sibling .dsh-mobile-control__trigger, which agree:

PropertyValueWhy
colour--dsw-alias-label-primarythe gear uses the primary label colour; a secondary-coloured icon beside it is a visible mismatch
height42pxthe footer row height both host controls use
padding0 10px 0 8px, gap 8pxthe gear's own padding and icon/label gap
radius--dsw-radius-mdthe gear's radius, not the small one
label14px / 22px, font: inheritthe gear label's typography
row boxcalc(100% + 4px), margin 4px -2pxthe gear's row is .wCInkW_triggerRow{width:calc(100% + 4px);margin:4px -2px} — it bleeds 2px past the column on each side, so its content begins at parentLeft − 2 + 8 = +6. Without the same bleed this row's icon and label sit 2px to the right of the gear's, and the two never line up. This button is its own row, so it carries both the bleed and the padding.

Verified on a real phone screenshot (1216×2640, so ≈3.5 device px per CSS px), measuring the dark ink of each row directly:

row, on the deviceicon ink leftlabel ink lefticon inkrow centre
before the bleed: 重启73157502356.5
before the bleed: 设置67150482531.5
after the bleed: 重启6615050—
after the bleed: 设置6715048—

So the old geometry sat +6 device px (≈2 CSS px) right of the gear — the 2px the CSS predicted — and the shipped geometry lands within 1 device px on the icon and exactly 0 on the label. The two row centres are 175 device px apart, which is 175 / 3.5 = 50.0 CSS px — precisely the designed 21 + 4 + 4 + 21.

真机对齐:橙色线为齿轮图标左缘

The orange line is the gear's own icon left edge. Before, the ↻ icon and the 重启 label both sit to its right; after, they land on it.

The equivalent check against the host's shipped CSS in a controlled 340px column gives the same result to the hundredth:

rowicon leftlabel left
the gear5.6729.67
this button, before the bleed7.67 (+2.00)31.67 (+2.00)
this button, after the bleed5.67 (0.00)29.67 (0.00)

受控 CSS 校验

One residual is not a CSS mismatch and is deliberately left alone: on the device the ↻ ink is 50px against the gear's 48px. Both icons are the host's own primitives at the host's own wide ? 16 : 18, drawn on the same 16px box with the same 1.3 stroke — a near-full circle simply carries more ink than a compact gear. Making the two inks match would mean drawing the shipped artwork below the size the host asks for, which is the one thing this row must not do.

The label follows the host's own wide convention — no invented breakpoint. The gear's TriggerContent renders wide ? <icon size 16> + label : <icon size 18>, and dsh-mobile, the seat's other occupant, does wide ? <label> : undefined.

The two icons can never disagree, because both seats are handed the same wide. In the shipped sidebar there is one source:

const wide = windowsTitlebar ? !collapsed : !collapsed || !settled;
renderSlot("sidebar.footer.action", { wide })   // this plugin
renderSlot("sidebar.settings",      { wide })   // the gear

and the gear forwards it unchanged to settings.trigger. So this plugin's wide ? 16 : 18 is the gear's wide ? 16 : 18, by construction.

The registered component must forward the seat's props. One that takes no parameters drops wide, and the control then stays in its icon-only shape in every state while still rendering "fine" — a defect invisible in review and obvious on a phone. That is exactly what the first mobile screenshot showed: a bare icon in the open wide drawer, above a labelled 设置. test/client.test.mjs now pins the forwarding, and the check was confirmed by reintroducing the bug and watching it fail rather than trusting a green run.

  • Wide column (desktop sidebar; also the mobile drawer while it is open): icon + the word 重启 / Restart, in the gear's font and colour.
  • Collapsed 56px rail: 36×36, icon grown to 18px, no label — the gear's rail state. Note this is the one place the two host references differ, and the gear wins: .wCInkW_trigger.wCInkW_rail keeps --dsw-radius-md (12px), while dsh-mobile's .is-rail swaps to border-radius: 50%. A circle beside a rounded square reads as a different control family, so this plugin uses the gear's rounded square.

On mobile specifically (dsh-mobile, overlay breakpoint (max-width:720px), the drawer is min(88vw, 340px)): the mobile bundle's !important rules target the composer, the settings options page and sidebar row menus — none of them touch sidebar.footer.action or the gear, and mobile defines no icon-size variable. It only reads the host sidebar's own _collapsed class to decide whether the drawer is open. So the two icons stay in step by construction, not by a mobile-specific override.

Two skins/layouts are served by the same code, with no branching: with a skin that folds the footer into the account popover (for example dsh-claude-style's collapseFooter, on by default), the skin hoists this same entry into its menu and renders it as a menu row — where the identical icon still reads as part of the set.

The glyph is the shipped IconRefreshOutlineArtwork from @deepseek-ai/dsh-client-ui-primitives, copied rather than imported (a plain-JS bundle may not load a Harness Client package, and importing one is what the plugin rules forbid). Same 16×16 box, same 1.3 medium stroke, same currentColor and default caps, so it reads as part of the host's own icon set.

Why the restart works this way

The obvious implementation is not available, and each dead end was verified against the installed build rather than assumed:

  • The button cannot go inside the account menu. Its items (settings, contact, signout) are a hard-coded array in @deepseek-ai/dsh-client-ui-settings-account; the menu exposes no plugin seat.
  • There is no restart API for plugins. window.dshDesktop exposes only browser, deviceInfo, keyboard, shortcuts and updates; none of the 25 dsh-desktop:* IPC channels restarts anything. The Host is a plain Node child of the Electron main process (ELECTRON_RUN_AS_NODE=1), so it cannot call app.relaunch(). The built-in "Restart App and Host" menu item exists but is gated on !app.isPackaged, so a released build never shows it.
  • Closing the window does not quit. window.on('close') hides to the tray. Launching the app again is also a no-op: the single-instance lock turns the second launch into "focus the existing window".

So the restart is an out-of-process handoff: the Host answers 202, then a detached helper kills the Electron main process and the Host by PID, waits until both are really gone (which is what keeps the relaunch from being demoted by the single-instance lock), and starts the application again with the Node-mode marker removed.

What the tab does while that happens

The page cannot be told "restart finished" by a connection that is being torn down, so after the 202 it polls the status route and compares the boot id. A different boot id means the new process is answering, and the tab then calls location.reload(). That reload is the point of the whole feature: it is what puts the browser on the newly installed bundle instead of leaving it on the old code — and it is why the progress dialog is dismissable (Escape, the scrim, or its button) rather than a modal trap if the handoff never lands. The poll is read-only and stops after POLL_LIMIT attempts, handing the UI back.

Endpoints

Both are same-origin, loopback-only.

RouteMethodPurpose
/dsh-desktop-restart/statusGETboot id, PIDs, and whether a restart is possible here (with the reason when it is not)
/dsh-desktop-restart/restartPOSTperforms the restart; answers 202 before the app is killed

POST refuses any request that carries a forwarding header, does not come from loopback, has an Origin that disagrees with Host, or is not application/json (a cross-site page cannot send that content type without a preflight this server does not answer). A single-flight latch makes a second click during a restart a no-op instead of a second application.

Settings

KeyDefaultMeaning
handoffDelayMs400How long the helper waits before killing, so the 202 reaches the browser.

Logs

Each restart writes dsh-desktop-restart-<timestamp>.log to the system temp directory; the path is shown in the progress dialog and returned by the 202.

Limits

  • It is a hard restart. It does not run the shell's quit inspection, and running tasks are interrupted — which is why the button confirms first.
  • Freshly installed bundles may still need one manual restart to load. After that, this button is the way to restart.
  • If a future DSH release changes the process model, the plugin degrades to a disabled button with a stated reason rather than damaging the app.

Development

node --check index.js && node --check client.js
npm test

test/client.test.mjs pins the parts of the client half that are invisible until they are wrong: that it loads React and no Harness package, that the glyph is the official artwork rather than a redraw, that the control matches the gear row's colour/metrics/typography, that the label follows the host's wide convention, that every visible colour is a theme token, and that its only mutation is the JSON restart POST.