xieani090612/dsh-remote-panel ↗★ 1

dsh-remote-panel

DSH 远程/WSL 状态预览:探测本机 WSL 发行版与指定 IP 的 SSH 机器(CPU/内存/磁盘/GPU/Docker/进程/服务),渲染到独立 WinUI 3 悬浮窗,并提供 /wsx 命令、remote_* 原生工具、Skills 与 MCP stdio 服务器 适合在DSH中频繁使用远程服务器或WSL环境进行开发运维的用户。

Package
dsh-remote-panel
Compatibility
Unverified
Version
0.1.1
License
MIT
Last updated
Oct 3, 2026

Install

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:xieani090612/dsh-remote-panel

配置目标

目标写在 profile 的 cordis.patch.yml 里,位于 dsh-remote-panel 那一行的 config.targets 下。改完重启 DSH。

- id: dsh-remote-panel
  name: dsh-remote-panel
  config:
    targets:
      # A local WSL distribution
      - kind: wsl
        name: WSL Ubuntu 24.04
        distro: Ubuntu-24.04
        channel: auto          # auto = try SSH first, fall back to wsl.exe
        user: root             # optional: the default user for a WSL target is "root"
        tags: [local]
        services: [ssh, docker, cron]

      # A remote machine (IP or hostname)
      - kind: ssh
        name: lab-gpu-01
        host: 192.168.1.50
        port: 22
        user: ubuntu
        identityFile: ~/.ssh/id_ed25519
        tags: [lab, gpu]

      # Or just use a Host alias from ~/.ssh/config
      - kind: ssh
        name: prod-web-1
        host: prod-web-1
        user: deploy

目标可以用 id、name、主机名,或其中任意一个的唯一片段来选中。省略 id 时,它由 kind + user + host 推导出来——例如 ssh-ubuntu-192.168.1.50。

配置项参考

选项默认值说明
enabledtruefalse 会停止所有探测。插件仍然加载,命令/工具/面板仍然可用,但每个目标都停在 unknown。
probeIntervalMs15000探测间隔(钳制在 3000–3600000)。
timeoutMs60000全局单次探测超时(钳制在 3000–600000)。WSL 冷启动要花 18–88s —— 如果你会盯着 WSL,别调低这个值。
targets[].timeoutMs继承全局值单目标覆盖(同样是 3000–600000 的钳制)。只收紧某一个目标——比如一直温热的 WSL 发行版,或纯 SSH 部署。
connectTimeoutMs10000建连超时(ssh 的 ConnectTimeout)。真正能逮住“机器确实挂了”的就是它,所以全局那 60s 永远不会让你白等。
probeOnStarttrue插件启动时探测一次(错峰进行)。
maxConcurrentProbes4同时探测多少个目标(钳制在 1–32)。
allowMutationstrue写操作总开关;false 即保持只读。
collectDockertrue采集开关——关掉能减轻目标端负载。
collectProcessestrue
collectServicestrue
collectGputrue
historyLength40每个目标保留多少条历史样本用于趋势线(钳制在 0–600)。一次失败仍会推入一个 latencyMs=0 的断点。
flushIntervalMs500调度器 tick 周期(钳制在 100–60000)。
heartbeatMs2000什么都没变时多久重写一次快照——面板靠它区分“没变化”和“host 没了”(钳制在 500–300000)。
autoLaunchtrueDSH 启动时打开面板窗口。
appPath''面板 exe 的显式路径;留空表示“在包内查找”。
stateFile$DSH_HOME/remote-panel/state.json快照写入的位置。
targetsFile—从一个 JSON 文件读取额外的目标数组。
ssh.executable自动检测显式指定 ssh.exe。
ssh.user''kind: ssh 目标的默认用户。
ssh.port22kind: ssh 目标的默认端口。
ssh.identityFile''默认身份文件。
ssh.commonFlags[]追加到每次 ssh 调用后的额外 argv。
ssh.controlMastertrue连接复用。热探测从 ~300ms 降到 ~80ms。
ssh.controlPersistSec60复用的 master 保持存活多久(钳制在 0–3600)。
wsl.executablewsl.exe
wsl.defaultUser''目标没有 user 时使用。

单目标选项:kind(wsl | ssh)、id、name、distro、host、port、user、channel(auto | ssh | wsl)、identityFile、sshConfigHost、strictHostKeyChecking、timeoutMs、tags、enabled、services。kind: wsl 目标还可以设 sshPort(默认 2222)——那是通过 SSH 访问该 WSL 目标时使用的端口,这类目标的 host 侧永远是 127.0.0.1。

为什么 timeoutMs 默认是 60000。 最后一个 wsl.exe 会话结束约一分钟后,WSL 会把整个发行版拆掉,所以冷启动的第一次探测要花 18–88s。早先 20s 的默认值让健康的 WSL 目标每隔几轮就抖成 “offline”——数据是对的,只是还没到。真正的浪费(等一台根本连不上的机器)改由 connectTimeoutMs 吸收,所以 60s 只作用于“连得上但很慢”的目标——而这正是 WSL 冷启动的样子。已经温热的部署可以用 targets[].timeoutMs 收紧单个目标。


配置项参考

选项默认值说明
enabledtruefalse 会停止所有探测。插件仍然加载,命令/工具/面板仍然可用,但每个目标都停在 unknown。
probeIntervalMs15000探测间隔(钳制在 3000–3600000)。
timeoutMs60000全局单次探测超时(钳制在 3000–600000)。WSL 冷启动要花 18–88s —— 如果你会盯着 WSL,别调低这个值。
targets[].timeoutMs继承全局值单目标覆盖(同样是 3000–600000 的钳制)。只收紧某一个目标——比如一直温热的 WSL 发行版,或纯 SSH 部署。
connectTimeoutMs10000建连超时(ssh 的 ConnectTimeout)。真正能逮住“机器确实挂了”的就是它,所以全局那 60s 永远不会让你白等。
probeOnStarttrue插件启动时探测一次(错峰进行)。
maxConcurrentProbes4同时探测多少个目标(钳制在 1–32)。
allowMutationstrue写操作总开关;false 即保持只读。
collectDockertrue采集开关——关掉能减轻目标端负载。
collectProcessestrue
collectServicestrue
collectGputrue
historyLength40每个目标保留多少条历史样本用于趋势线(钳制在 0–600)。一次失败仍会推入一个 latencyMs=0 的断点。
flushIntervalMs500调度器 tick 周期(钳制在 100–60000)。
heartbeatMs2000什么都没变时多久重写一次快照——面板靠它区分“没变化”和“host 没了”(钳制在 500–300000)。
autoLaunchtrueDSH 启动时打开面板窗口。
appPath''面板 exe 的显式路径;留空表示“在包内查找”。
stateFile$DSH_HOME/remote-panel/state.json快照写入的位置。
targetsFile—从一个 JSON 文件读取额外的目标数组。
ssh.executable自动检测显式指定 ssh.exe。
ssh.user''kind: ssh 目标的默认用户。
ssh.port22kind: ssh 目标的默认端口。
ssh.identityFile''默认身份文件。
ssh.commonFlags[]追加到每次 ssh 调用后的额外 argv。
ssh.controlMastertrue连接复用。热探测从 ~300ms 降到 ~80ms。
ssh.controlPersistSec60复用的 master 保持存活多久(钳制在 0–3600)。
wsl.executablewsl.exe
wsl.defaultUser''目标没有 user 时使用。

单目标选项:kind(wsl | ssh)、id、name、distro、host、port、user、channel(auto | ssh | wsl)、identityFile、sshConfigHost、strictHostKeyChecking、timeoutMs、tags、enabled、services。kind: wsl 目标还可以设 sshPort(默认 2222)——那是通过 SSH 访问该 WSL 目标时使用的端口,这类目标的 host 侧永远是 127.0.0.1。

为什么 timeoutMs 默认是 60000。 最后一个 wsl.exe 会话结束约一分钟后,WSL 会把整个发行版拆掉,所以冷启动的第一次探测要花 18–88s。早先 20s 的默认值让健康的 WSL 目标每隔几轮就抖成 “offline”——数据是对的,只是还没到。真正的浪费(等一台根本连不上的机器)改由 connectTimeoutMs 吸收,所以 60s 只作用于“连得上但很慢”的目标——而这正是 WSL 冷启动的样子。已经温热的部署可以用 targets[].timeoutMs 收紧单个目标。


Usage

Deeper references (written alongside this README):

/wsx ... commands

/wsx                    status overview of local WSL and remote machines
/wsx status [target]    status overview (optionally one target)
/wsx list               list all configured targets and their channel
/wsx probe [target]     probe now (all targets when none is given)
/wsx docker [target]    list containers
/wsx services [target]  list key service states
/wsx ps [target] [n]    list processes by CPU
/wsx exec   run a command on the target
/wsx panel              show / raise the desktop panel window
/wsx open               open the plugin data directory and the state file
/wsx help               the command list above

/wsx list also prints a tool registration path line, showing whether the tools were registered through the official defineTool helper (with extra schema validation) or fell back to raw JSON Schema. Both work.

On the machine this was developed on the fallback (raw) path is what runs, and the reason is worth recording: the @deepseek-ai/* packages exist only inside app.asar and are not unpacked to disk, so a third-party plugin's ESM import('@deepseek-ai/dsh-tools') cannot resolve in Node (Cannot find package '@deepseek-ai/dsh-tools').

So the plugin translates its own parameter dialect into standard JSON Schema via projectParameters() and registers through ctx.tools.register's raw path — a path that uses nothing but Node builtins and is therefore always available. The registration-path line reports honestly which path is in use; seeing raw is not a fault. Behaviour, argument validation and the safety gates are identical either way.

Agent tools

Seven remote_* tools, sharing one implementation and one set of gates with the MCP server:

ToolPurpose
remote_statustarget overview; refresh: true re-probes immediately
remote_execrun a command on the target, return stdout/stderr/exit code
remote_fileslist / read / upload / download
remote_dockerps / logs / start / stop / restart / pause / unpause
remote_servicesstatus / start / stop / restart / reload
remote_processeslist (by cpu/mem) and kill
remote_panelshow the WinUI 3 panel window

MCP

cordis.patch.yml already carries a row that exposes the same capabilities over MCP stdio:

- id: mcp-remote-panel
  name: '@deepseek-ai/dsh-mcp-client'
  config:
    serverName: remote_panel
    transport: stdio
    command: !!js process.execPath
    args: !!js "process.env.DSH_PROFILE_DIR ? [process.env.DSH_PROFILE_DIR + '/node_modules/dsh-remote-panel/bin/mcp-server.js'] : []"

The tools appear as mcp__remote_panel__. Delete that whole block if you do not want MCP — nothing else is affected. (The real file also sets cwd, failOnStartupError: false and toolCallTimeoutMs; the excerpt above is trimmed to the parts that matter for understanding how it is wired.)

To check the MCP server by hand, without DSH:

$env:DSH_WSX_CONFIG = '{"config":{"targets":[{"kind":"wsl","distro":"Ubuntu-24.04","channel":"wsl","user":"root"}]}}'
'{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node .\bin\mcp-server.js

stdout must carry protocol JSON only (newline-delimited); all diagnostics go to stderr. When adding logging to that file, write to stderr — one extra character on stdout breaks the protocol.

The target list comes from the config.resolved.json the host plugin writes, so the MCP side and the DSH side always see the same targets; configuration merging is not reimplemented there.

Skills

Two skills, loadable by the agent once installed:

SkillPurpose
remote-panelwhich surface to use when, typical tool usage, the two WSL channels and cold starts
remote-panel-troubleshootinglayered diagnosis: config → link → collection → state file → MCP → refused writes

They are installed into DSH's default skill scan root, $DSH_HOME/skills/, by install.ps1 (and by the manual steps in INSTALL.md) rather than through a bundle patch. The long comment in cordis.patch.yml explains why a bundle patch cannot enable skill-filesystem here (the web-app layer disables that row, and a non-insert patch only assigns the keys it carries, so it can never clear the inherited disabled). Edits to skill content take effect live — that directory is watched; code and config changes need a restart.