@ray1270/dsh-restart:給 DSH 桌面版加刷新與一鍵重啓

前言

DSH 桌面版的端口每次啓動都是隨機分配的(--port 0),所以每次重啓都等於換了一個 origin:舊頁面失聯、殼自動開新頁面、你被扔回首頁。想「重啓一下看效果」,原本要關窗口、找圖標、等啓動、手動點回會話。客戶端插件的改動(slot 註冊、界面代碼)本來只需要重新加載頁面就能生效,但手按 F5 會讓當前會話掉回首頁——因爲 DSH 的當前會話只活在頁面裏、不在 URL 上。

下面介紹的 @ray1270/dsh-restart 就是解決這兩件事的客戶端插件。

這是什麼

維護者 Ray1270(package.json 裏 author 爲 Ray_lv)發佈在 npm 上的客戶端插件(dsh.client.platform = web),當前版本 0.3.7,MIT 協議。它給 DeepSeek Harness 桌面版的窗口頂欄加兩個常駐按鈕:

應用  編輯  刷新  重啓

刷新只重裝瀏覽器這一半,Host、會話記錄、正在跑的 agent 都不動;重啓會重啓整個應用(Electron 殼 + Host),但回來時停在你剛纔那個會話。

兩個按鈕分別做什麼

刷新:重新加載當前頁面。Host 不動、會話記錄不動、正在跑的 agent 也不動,只把 Web 客戶端重新裝一遍。加載完仍然停在剛纔那個會話(會話標記 2 分鐘內有效)。代價是輸入框裏沒發出去的草稿會丟,已發送的對話、正在生成的回覆、後臺 job 都不受影響。由於不跨 origin,刷新這條鏈路上沒有任何一次網絡請求。

重啓:重啓整個應用。第一點是預檢,按鈕顯示「確認重啓 · N 項」,N 是此刻會被中斷的 agent / job / 終端數量,4 秒不點自動撤銷;第二點是執行,1–3 秒後應用退出並自動拉起,回來時停在同一會話(30 分鐘內有效)。確認過就是真中斷:會話記錄是追加寫,丟的是進行中那一輪和 job 輸出。

之所以刷新一下、重啓兩下:刷新不殺服務端任何東西,代價最大隻是草稿;重啓會真的中斷活動任務,必須讓人看清數字再決定。

點失敗(重啓失敗 / 刷新失敗)時按鈕 6 秒後復位,原因在 tooltip 裏。

安裝與啓用

已發佈到 npm,一行命令:

dsh plugin --profile desktop add @ray1270/dsh-restart

裝完重啓一次應用纔會加載(這一次只能手動重啓,雞生蛋問題),之後就靠這兩個按鈕了。

dsh plugin add 走的是 pnpm add 到 profile,再把包名追加進 dsh.profile.bundles,不用手改任何文件。應用裏的「插件 → 添加插件」走的是同一條路,只是多一個填路徑的輸入框。

不走 npm 的話,到 Releases 下載最新的 ray1270-dsh-restart-<版本>.tgz,在「插件 → 添加插件」裏填它的絕對路徑。兩條路裝的是同一個文件——release 附件與 npm 上那份 tgz 的 sha256 逐字節相同。想自己覈對:

Get-FileHash .\ray1270-dsh-restart-<版本>.tgz -Algorithm SHA256   # 與同名 .sha256 文件比對

從源碼裝(開發):

dsh plugin --profile desktop add "<這個目錄的絕對路徑>"

裝完同樣要重啓一次應用。

配置

寫在 profile 的加載行 config: 裏(也可以直接改 cordis.patch.yml):

鍵 默認 含義
enabled true 重啓總開關;false 時重啓報「不可用」並拒絕一切重啓請求(刷新不受影響)
delayMs 1200 動手前的等待,用來保證 HTTP 回執先到瀏覽器
hostGraceMs 8000 等 Host 自己優雅退出的上限,超時才強殺
killTimeoutMs 15000 等舊進程徹底消失的硬上限
pendingTtlMinutes 30 「重啓後回哪個會話」標記的有效期

刷新沒有配置項:它不碰 Host,也就沒有可配置的危險動作。

刷新是怎麼做的

純客戶端,Host 不參與:

  1. 把當前 sessionId 寫進 localStorage(同源,重載不丟);
  2. location.reload();
  3. 頁面重新加載時 client 插件重新拉取、重新掛載,讀到標記後 uiWorkspace.openSession(sessionId),再刪掉標記。

刷新不跨 origin,所以標記放瀏覽器存儲就夠了,不必走 Host 文件。

重啓是怎麼做的

由 Host 側排程,helper 由 WMI 服務啓動,繞過應用自身的進程樹。整體流程:

  1. 瀏覽器 POST /api/dsh-restart/restart { sessionId, force };
  2. Host 做活動工作檢查(force 才放行),寫 pending-restore.json(重啓後要回的會話);
  3. 起一個短命的 powershell,調用 Win32_Process.Create → 由 WmiPrvSE.exe 創建 helper.ps1(用 Win32_ProcessStartup 的 ShowWindow = 0 讓它生來沒有窗口);
  4. 等 helper 寫出 boot 狀態後纔回 200;
  5. helper 複覈 ShellPid 的鏡像路徑仍等於目標 exe(防 PID 複用),taskkill /F 只殺 Electron 殼(不加 /T),等 Host 自己優雅退出(最多 8 秒),再 Start-Process 重新拉起 exe(失敗重試一次並校驗新進程還活着),結果寫 restart-state.json;
  6. 殼一死,Host 的 IPC 管道斷開,Host 走自己的 application.shutdown.shutdown(0) 優雅退出(5 秒上限)。

幾個關鍵點:

  • 只殺殼,不殺樹。 Host 子進程註冊了 process.once("disconnect", () => stop()),殼一死它就自己走完整 dispose 流程,所以「硬殺」只硬在殼這一下。
  • helper 必須活着跨過殼死掉那一刻。 這個應用的子進程樹不活過殼,普通子進程做不到(表現就是「只有關、沒有開」)。Win32_Process.Create 由 WmiPrvSE.exe 執行,helper 的父進程是 WMI 服務,殼死時它毫髮無損;helper.log 的 boot 行會記下 parent=…(WmiPrvSE)。
  • 不用 detached: true。 Windows 上 Node 用 detached 啓動 powershell.exe 會讓它立刻以 0 退出,一行都不執行;真正讓 helper 活下來的是「由誰創建」。
  • WMI 啓動失敗就放棄重啓。 短命 launcher 把結果寫進 launch-result.json;Host 等不到 helper 的 boot 或 WMI 明確拒絕,就回 500 並寫 cancel-restart 標記,還在延遲中的 helper 看到會立刻放棄。
  • 創建的進程要生來隱藏。 -WindowStyle Hidden 救不了(那是窗口存在之後纔去隱藏),必須構造 Win32_ProcessStartup 交給 Create。

生態裏現成的安全重啓是 ctx.appExit(0),但它在桌面版等於讓 Host 自殺,殼會判定異常終止並彈出「應用意外停止」恢復框,多一次點擊、日誌裏留一次假崩潰。這個插件選的是「殺殼 + 重新拉起」,所以能一次點完。

發生問題時按可能性排序自查:

  1. 點了沒反應、應用還在——helper 沒起來(WMI 被禁用/攔、腳本被刪)。此時應用完全沒受影響。先看 ~/.dsh/dsh-restart/launch-result.json,再看 helper.log。
  2. 應用退出了但沒回來——helper 在殺之前就死了,或 Start-Process 被攔。手動雙擊 D:\dsh\DeepSeek Harness.exe;restart-state.json 寫着停在哪一步。
  3. 點「刷新」後回到首頁——會話標記沒能寫下(無痕模式、存儲被策略關閉)或會話已被刪,刷新鏈路失敗一律靜默退回首頁。
  4. 想徹底繞過這一切——托盤右鍵退出,再雙擊圖標。

狀態文件都在 ~/.dsh/dsh-restart/:restart-state.json(最近一次重啓階段)、helper.log(帶時間戳的過程日誌)、launch-result.json(WMI 創建 helper 的結果)、pending-restore.json(待恢復會話標記,被消費後刪除)、cancel-restart(僅在 Host 放棄啓動時短暫出現)。

適用場景與注意

適合在 DeepSeek Harness 桌面版裏調試客戶端插件、或常需要重啓應用看效果的人。刷新在任何環境都可用;重啓只支持桌面版,CLI / headless 的 Host 沒有殼可重啓,按鈕會明確報「不支持」。

注意幾點:

  • 插件以當前 dsh 進程的權限運行,安裝前建議先看源碼與許可證(MIT)。
  • 頂欄入口依賴桌面殼自己畫的菜單條(preload 用 data-windows-menu 標記 + open shadow root),插件只能在它後面追加同級按鈕;探測不到就靜默不掛載,回落到會話標題欄最右側。兩條入口共用一套邏輯,頂欄那份優先。
  • 同一時刻只有一個入口在屏幕上:頂欄掛載成功時標題欄那對渲染成空,但仍掛着(它是唯一拿得到 sessionId 的地方,用於刷新/重啓後的會話回填)。
  • 重啓依賴 WMI 服務。WMI 被策略關掉、或 Win32_Process.Create 被安全軟件攔住時,重啓會明確拒絕(應用不受影響),錯誤和返回碼寫在 launch-result.json。
  • 殺軟可能好奇:「隱藏窗口的 PowerShell 殺掉應用再拉起」是 EDR 的經典啓發式。helper 是明文、帶完整註釋、固定路徑、日誌可查,沒有做任何編碼混淆。
  • 極端情況下仍可能看到一個控制台窗口:某臺機器上 Win32_ProcessStartup 構造失敗時 helper 會退回可見窗口啓動(重啓本身照常),launch-result.json 裏 windowHidden 會是 false。

小結

刷新解決「客戶端改動只需重裝頁面、又不想丟當前會話」,重啓解決「重啓後被扔回首頁」。兩者都以會話爲中心,代價提示和失敗原因都寫在界面上和日誌裏。

目錄頁:https://www.skillhub.cn/plugins/Ray1270/dsh-restart
倉庫:https://github.com/Ray1270/dsh-restart
npm:https://www.npmjs.com/package/@ray1270/dsh-restart

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜