Windows
Windows 支援是實驗性的。Herdr 在那裡的 API 是具名管道,而位於 socket 路徑的檔案(預設是 %APPDATA%\herdr\herdr.sock)只是一個記著 server 的 pid 與啟動時間的標記。這個套件裡每個用戶端(擴充、它的監看、側邊欄外掛)都連到 \\.\pipe\ 後接 socket 路徑,如同 Herdr 自己的 CLI。socket 路徑的找法與 Herdr 一樣:HERDR_SOCKET_PATH,否則是 HERDR_SESSION session 的 socket,否則是 Herdr 設定資料夾裡的預設值(XDG_CONFIG_HOME,否則 %APPDATA%)。
細節
sb server 的控制 socket 也是一個具名管道。管道名稱是整台機器上每個帳號共用的一個命名空間,而守著 Unix socket 的 700 資料夾在這裡派不上用場,所以 server 在啟動時隨機為它的管道取名(\\.\pipe\sb-<16 hex>),並把名稱記在你個人設定檔裡的 server.json,由 session 在那裡讀取。另一個帳號在管道存在之前無從得知名稱,但管道存在之後名稱可以被列出,而結束的 server 留下一個任何人都能拿走的空名。
所以管道和 Unix socket 一樣使用握手(src/server/socket.ts,控制協定 2):server 在啟動時抽一個新的 32 位元組機密,寫進 server.json(模式 600),而機密從不經過管道。用戶端以 hello 加一個新的 nonce 開頭;server 回答它自己的 nonce 與 HMAC(secret, "sb-control server" + both nonces);用戶端檢查它,然後才連同請求送出它自己的證明("sb-control client",同樣的 nonce),而 server 只在那個證明通過後才動作。
占用已死 server 名稱的東西學不到任何可以重用的東西(它看得到 nonce,卻無法做出證明),也永遠拿不到請求;從不回答的對端花掉 hello 的 5 秒,而不是整段等待。這擋住占用已無 server 的名稱的人;它擋不住坐在用戶端與一個執行中的 server 之間、雙向轉送的行程。請求寫出之後的失敗(server 關閉了連線,或沒有在時限內回答)是「請求可能仍會被執行」:用戶端不會再送一次。
更新之後,仍在執行的行程保留它們啟動時的協定:新套件的 session 會在它的啟動時間證明那就是那個行程之後,在 server 鎖之下,以 SIGTERM 結束協定 1 的 sb server(它的 server.json 沒有 proto),並啟動一個新的:更新後的第一個 session 重啟 sb server。已經講協定 2 並有回答的較舊 server,會被平順地升級(重啟請求),絕不發訊號。
有一條規則適用於啟動 server 的每一種方式(ensure、session 的 look、/boss server、up、塔台的詢問、restart 與 serve),以及每個 stopped 標記(stop):當 server.json 指名一個執行中卻沒有回答 hello 的行程時,不啟動 server,也不標記任何東西為已停止,除非這個套件對那個行程發了訊號並看到它離開(launch 與 stopped 標記自己檢查,不論呼叫者之前看到什麼)。
這樣的 server 被告知為放著不管,status 顯示它為無法連線,而 restart、stop、up 與塔台的詢問都以一則指名能結束它的指令的訊息而失敗(Windows 上是 taskkill /F /PID n,沒有視窗的行程需要 /F;其他地方是 kill n,卡住的迴圈讓它繼續跑時用 kill -9 n);session 的 look 連續兩次發現這樣的 server,會用這些話告訴老闆一次,並只在暫停解除又再回來之後才再告訴;這條規則同樣涵蓋只是慢或卡住的 server(在 Windows 上,逾時的 hello 也要等啟動時間,所以時間還沒被快取的卡住 server 會被暫停,而不是被第二個 server 加入),而啟動時間讀到了卻不是記錄裡那個的行程(pid 之後被重用)不是那個 server。
在 Windows 上,依據先前快取的時間而下的暫停判定,會先重讀一次,所以之後被重用的 pid 不會被暫停,它的號碼也不會被指名。在 Windows 上啟動時間來自一個要 3.5 到 5 秒、負載下更久的 PowerShell 呼叫:拒絕握手的 server 的第一次 look 最多等它 3 秒,而在那段等待裡沒讀到的時間,會讓 server 放著不管,由 status 說無法讀到啟動時間,且下一次 look 會自己再讀一次(讀取在背景繼續,而且同一時間最多跑一個,所以 session 每 2 秒的 look 不會每次都啟動一個 PowerShell)。
在發訊號之前,呼叫之前快取的時間會被忘掉並重讀,而那個讀取會等到一次讀取所需的時間(15 秒,或呼叫者剩下的時間);同一次呼叫讀到的時間照原樣使用。如果讀取在那段時間內沒有回來,或不是記錄裡的,就不發任何訊號、不啟動任何東西,也不標記任何東西為已停止;稍後的一次 look 會發現時間已讀到,然後才結束那個 server。對端在證明機密之前說的任何東西,都不會顯示在錯誤裡。
轉送需要使用者自己的帳號(存活管道的第二個實例,或在 700 資料夾裡取代 socket)。舊套件的 session(用 /reload 重新載入 pi)會被較新的 server 告知不相符。管道隨它的 server 結束,所以沒有檔案要移除。
行程的啟動時間沒有 Node API,而 Windows 沒有 ps,所以在那裡它是透過 PowerShell 的 CIM 查詢讀取的(powershell.exe -NoProfile -NonInteractive -Command "$ErrorActionPreference='Stop'; (Get-CimInstance Win32_Process -Filter 'ProcessId=<pid>').CreationDate.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ', [cultureinfo]::InvariantCulture)",隱藏,15 秒限制),它對服務或提升權限的行程也不需提升權限就能回答,而 Get-Process 做不到。一次呼叫在 Windows 11 ARM VM 上要 3.5 到 4 秒,負載下更久,所以沒有任何東西等它:pi 在它的 session 啟動時於背景讀自己的啟動時間,失敗時在 1、2、4、8 與 15 秒後重試,並在它的生命期間保留;別的行程的時間依 PID 快取十分鐘,在那個 PID 被看見已死時忘掉,而失敗的讀取在 5 秒後再問,然後是 10、20、40,最長 60 秒。
在第一個答案之前,啟動時間是未知的,算作「無法判斷」(記錄成立)。在自己的啟動時間已知之前寫的記錄(gui.json、remote.json、連線持有者的 holder.json),在它到達時再寫一次;「could not record its own start time」警告只在讀取放棄或那次寫入失敗時才出現。有了啟動時間,倖存者會像在 macOS 上一樣,接手擁有者已死的 GUI,而連線持有者的記錄與鎖不再被當成被別的行程重用的 PID。比較的兩邊都透過同一個函式讀取,所以 ISO 文字只需要穩定。平衡 pane 的分離式 node 以 windowsHide 啟動,所以不會出現 console 視窗。
側邊欄外掛宣告了 windows。Windows 沒有 bash,所以它的啟動 hook 與動作各有一個 Windows 的孿生版本,執行 node(Herdr 在 PATH 上找得到)而不是 run.sh,而且因為動作 id 在各平台之間是唯一的,Windows 的動作是 wayne930242.straw-boss.net-windows。Herdr 在 Windows 上的外掛支援本身是預覽。
專案信任轉送(專案信任)需要 pi 的 agent 資料夾在你的使用者設定檔之內。轉送在 <state>/trust/ 裡的檔案,只有在該資料夾是這個使用者的真實資料夾時才被信任,而 Windows 沒有使用者 id 可以據以判斷,所以資料夾必須位於 %USERPROFILE% 之下(Node 回報的家目錄,展開 8.3 短檔名之後比較),那底下的資料夾對使用者是私有的。當 PI_CODING_AGENT_DIR 指向別處(另一個磁碟機、共用資料夾)時,pi 回答 undecided,並像以前一樣在它的終端機裡詢問,而 GUI 永遠看不到這個問題;src/trust/files.ts(ensureDir)是這項檢查。
塔台 prompt 以 node "<cli.mjs>" prompt … 的方式輸入,cmd 與 PowerShell 接受它的方式和接受 sh 一樣,它透過和 status 相同的用戶端與 server 的具名管道交談(記錄在判斷出狀態資料夾的擁有者之後才讀)。它的退出按鍵是 Node 的:raw 模式裡的 Ctrl+D 與 Ctrl+C。在 Windows 上未執行。
pi quit 之後關閉 pane(塔台的 quit、/boss close)在 node 行程而不是 sh 裡等 pi 的退出,所以沒有 POSIX shell 也能運作。
Wi-Fi 存取(瀏覽器檢視)在 Windows 上運作,有以下差異。Windows Defender 防火牆會擋下未經請求的進入連線,除非有規則允許,而相符的已啟用封鎖規則會勝過任何允許規則。在私有位址上的第一次監聽,通常會問「Windows Defender Firewall has blocked some features of Node.js」;取消那個提示會為 node.exe 加入封鎖規則,而允許它需要系統管理員。server 自己不建立防火牆規則。
在一次不提升權限的 PowerShell 啟動中,它讀取每個所服務網路的類別、防火牆對它是否開啟,以及每條可能相符的已啟用進入規則(依程式、連接埠、位址與介面類型),然後依這個順序說明該怎麼做:適用的封鎖規則,附一條給提升權限 PowerShell 的 Disable-NetFirewallRule -Name '…' 指令(含萬用字元、控制字元或不可見字元的規則名稱,會從指令中省略並另外指名,改附 wf.msc 的步驟);沒有允許規則時,附一條針對連接埠上 TCP、LocalSubnet、Private 設定檔的 New-NetFirewallRule 指令;Windows 稱為 Public 的網路,附上要在哪裡把它設為 Private。
讀取失敗時會說明狀態未知,並給出兩條指令。讀取在監聽啟動之後於背景執行,從不卡住 GUI。電腦的名稱是它的 Windows 名稱轉小寫加上 .local;手機無法解析它時,QR 裡的位址仍然有效。Hyper-V 與 WSL 的轉接器會被服務(僅限本機 VM);硬體位址全為零的 VPN 轉接器,例如 WireGuard,則不會。哪些企業 VPN 轉接器會被服務,記錄在發行說明裡。
作為用戶端,Windows 瀏覽器把憑證安裝在目前使用者的「受信任的根憑證授權單位」,並把唯一一則安全性警告的 SHA-1 指紋,與圖磚顯示的那個比對(出現第二則警告就選否,並在 certmgr.msc 裡移除第一個)。
worktree reaper 仍然只支援 macOS 與 Linux。它只在 pane 的 shell 前景沒有執行任何東西時才關閉 pane,而在 Windows 上,除非有智慧體在它底下執行,否則 Herdr 的 pane process-info 只回報 shell,所以執行建置、server 或編輯器的 pane 看起來會是閒置的而被關閉。移除一個 checkout 也需要每個行程的工作目錄,lsof 與 /proc 能給,而 Node 在 Windows 上不用原生程式碼就讀不到。
tests/platform.test.ts 在每個平台上檢查這些,而 Windows workflow 在載入 session 與側邊欄外掛載入的每個模組之後,在 Windows runner 上對真實的具名管道執行它。Herdr 本身不在 CI 裡執行,所以 pi 在 Windows 的 Herdr 裡啟動,是以手動檢查的。