packages/dsh-task-board
zhu1090093659/dsh-web侧边栏多列任务看板:卡片交给真实 DSH 智能体会话执行,支持 cron 定时(Host 侧到点执行,关浏览器也生效)。
仅支持 Windows:注入 DSH 页面的电源卡片,图案左右各带一块命中区(左关机、右重启),第一次点击只把卡片变成一句是否确认,第二次点击才执行;重启引擎只停本实例的进程树,等待端口释放后在宿主 Job Object 之外重新拉起,并用插件自己的状态路由确认新实例已经应答。
Power actions for DSH (DeepSeek Harness) desktop profile: restart and shutdown via a draggable power card on the page.
Shift+click = dry-run only (checks endpoints without firing).Behind the scenes:
/api/dsh-power/status with a pid that started after the kill — "something is listening" is not enough, because a second instance answers just as well.lock-held-by-other instead of burning the whole budget, and a launch path that reported no pid at all is declared dead after ~6s and retried.From GitHub:
dsh plugin --profile desktop add github:nanshang328/dsh-power
Or from a local folder / ZIP download:
dsh plugin --profile desktop add ./dsh-power
Then restart DSH. The package declares dsh.bundle, so it mounts as a normal profile layer.
Plugin config (override the same id in your profile's cordis.patch.yml; a later patch
REPLACES the whole config object, so restate every key you still want):
- id: dsh-power
name: 'dsh-power'
config:
port: 0 # 0 = discover from webServer/--port
processName: DeepSeek Harness
exePath: '' # '' = use process.execPath
showRestart: true
showShutdown: true
confirmPower: true # both actions need a second click (confirmShutdown is the old name, still read)
confirmMs: 3000 # how long the pending question stays armed
foreground: true # bring the new window to the front after restart
logToTemp: true # write %TEMP%\dsh-power.log
isLoopback AND a custom x-dsh-power: 1 header. A random
web page cannot send that header cross-origin without a CORS preflight, which this route
does not answer — so drive-by "shut down the user's app" from an ad iframe is blocked.
(A no-cors fetch with only simple headers still reaches the server, which is exactly
why the header check exists.)-KillAllNamed restores the old
behavior explicitly; -Explain prints both sets without killing anything./power-status for confirmation.SetForegroundWindow; in that case logs will say foreground=refused.-Port 0, when you run the script yourself) has no status route to probe: readiness there is "the pid we launched outlived its first seconds", and it is watched by pid liveness rather than by the port.lib/index.js (host half), client/widget.js (the card), scripts/restart-dsh.ps1
(the restart engine). client/niang-sleep.webp is inlined into the widget by
tools/embed-icon.cjs (run python tools/build-icon.py first if you swap the art)..ps1 as ANSI, and Chinese comments can swallow newlines and break the parser.npm run check (node --check on both JS files); pwsh -File test/tree-test.ps1
(unit test for the multi-instance tree resolution against a fake process table);
pwsh -File testbed\run-test.ps1 -Scenario A|B|C and pwsh -File testbed\noport-test.ps1
(end-to-end runs of the engine against a fake app on ports 19501-19505 — they never touch
the real DSH); pwsh -File scripts/restart-dsh.ps1 -Explain -HostPid <pid> (dry look at
what a restart would kill on YOUR machine, kills nothing).CimException: 拒绝访问), the launch legitimately falls back to explorer, and a healthy
run then looks like a failure.~/.dsh/profiles/desktop/plugins/dsh-power and register it via
dsh plugin --profile desktop add <path>. Widget changes need only a page refresh
(the route re-reads the file per request); lib/index.js changes need a DSH restart;
the .ps1 is spawned fresh per action, so it is always live./api/dsh-power/status reply from a NEW pid, not "a pid exists".lock-held-by-other / launch-failed verdicts: retrying against a lock another instance
owns can never succeed, and the explorer/schtasks paths can report no pid at all — both
used to consume the entire start budget (30s per try) before saying anything.ready/ok in the status file. The helper now watches
for a while (outside the lock) and rewrites the record as died / teardown / replaced.-Port 0) no longer polls a budget it cannot satisfy and no longer reports
a healthy relaunch as teardown with exit code 3 — it watches the pid it launched.servingPid (the host pid the port actually named), so "up"
names the instance it verified instead of only the pid a launch path reported.MIT (copy of the pattern used by companion plugins).