Herdr —— Rust 終端多工器 + AI agent 協調器(試用)¶
Terminology rule (zh-TW pages)
技術名詞首次出現以「中文 (English original)」格式呈現,例:依賴注入
(dependency injection)。不自創翻譯——若無公認譯名直接保留英文
(如 embedding、tokenizer)。代碼、API 名、CLI flag、套件名、檔名一律不翻。
ogulcancelik/herdr 是一個用 Rust 寫的終端多工器 (terminal multiplexer),內建 coding-agent 感知——它會追蹤每個 pane 的 agent 狀態(idle / working / blocked / done)。它跟 tmux/zellij 屬於同一類工具,但更偏滑鼠優先 (mouse-first)、更 agent-native。官方文件:https://herdr.dev/docs/。
本 repo 把 herdr 當作一個與 tmux 共存的試用工具 (trial tool)——你只會執行 herdr 或 tmux,永不巢狀 (nested)。既有的 tmux / sesh / tmuxp / workmux 設定完全不動;herdr 純粹是加法,讓你能在不失去日常工具的前提下評估它。
- 安裝 (Install):
- macOS —— Homebrew(
herdr在 homebrew-core;由dot_ansible/roles/devtools/tasks/main.yml的 macOS 清單管理) - Linux —— GitHub release 的單一靜態 binary(
herdr-linux-{x86_64,aarch64})放到~/.local/bin/herdr(由同一 role 中# --- herdr ... ---區塊管理)。沒有 tarball,所以不需要解壓步驟。 - 驗證 (Verify):
herdr --version;用herdr server reload-config驗證設定檔 - 升級 (Upgrade):macOS 用 brew;自管的 Linux binary 用
herdr update - 設定 (Config):
~/.config/herdr/config.toml—— chezmoimodify_覆蓋層 (overlay)(dot_config/herdr/modify_config.toml.tmpl+ 受管本體.chezmoitemplates/herdr/config.toml)。覆蓋層在每次chezmoi apply強制套用我們受管的表 (tables),同時保留 herdr 在執行期寫回的東西(見 設定管理)。
不受
enableVimMode控制。 該 flag 管的是 shell + tmux 的 modal 編輯;herdr 的 copy mode(prefix+[)天生就是 vi 風格(h/j/k/l、w/b/e、{/}、v/Space 選取、y/Enter 複製、q/Esc 離開),沒有東西需要 gate。
與 tmux 的模型差異 (Model differences vs tmux)¶
herdr 的階層是 Session → Workspace → Tab → Pane——比 tmux(Session → Window → Pane)多一層。「Workspace」是專案層級的容器;「Tab」把多個 pane 分組。CLI(herdr session|workspace|tab|pane|agent …,大多支援 --json)是取代 tmux switch-client / list-sessions 等指令的腳本介面。
跟 tmux 最關鍵的差異:單一 herdr server 會託管多個具名 session (named sessions),每個 session 各自是一棵持久化的樹、各自有自己的 socket。最上層的 Session 這一層,比較接近「多個 tmux server / socket」,而不是 tmux 的 session——你在 tmux 會叫做 session 的東西(vibe/<repo>)在 herdr 對應的是 Workspace,而不是 herdr Session。
具名 session (Named sessions)¶
herdr—— 啟動或 attach default session(socket~/.config/herdr/herdr.sock)。herdr --session NAME—— 啟動/attach 一個具名 session(socket~/.config/herdr/sessions/<NAME>/herdr.sock)。herdr session list [--json]—— 列出所有 session 及其running+socket_path。--json的socket_path才是權威值——純文字表格印出的是herdr.socket,但真正的 socket 是herdr.sock。herdr session attach NAME·herdr session stop NAME·herdr session delete NAME(stop 時default是合法的NAME)。
從 CLI 子指令鎖定某個 session:workspace/tab/pane/agent 沒有 --session/--socket flag。唯一的槓桿是 HERDR_SOCKET_PATH 這個環境變數 (env var)——把它設成某 session 的 socket_path,所有子指令就會導向該 session。在 herdr pane 內部它已經被 export 成當前 session 的 socket,所以在 herdr 內跑的腳本天生就鎖定當前 session。hvibe --session NAME 正是靠這個機制運作(見下文)。
cwd 與 workspace 命名模型¶
herdr 追蹤 cwd 的方式跟 tmux 不同,會顛覆兩個常見預期(皆用 herdr pane list 驗證):
- 每個 pane 有兩個 cwd。
cwd= shell 的啟動目錄(spawn 時固定);foreground_cwd= 即時 cwd,透過 OSC 7 shell 整合追蹤。shell 裡cd會更新foreground_cwd;啟動cwd永不變。 - 子行程 / 子 shell 裡的
cd不會傳上來。 因為追蹤是 OSC 7-based,一個沒有再送 OSC 7 的子 shell 裡的cd——例如chezmoi cd,它會在 source 目錄 spawn 一個新shell——對 herdr 是隱形的。foreground_cwd不動,所以 space 的 git-repo 偵測與prefix+Glazygit 位置都不跟著子 shell 走。這是 OSC 7 的固有特性,不是 herdr bug——預期行為。 - 新 tab 開在 workspace 根目錄,不是聚焦 pane 的 cwd。 用
new_cwd = "follow"(見下)時,split 繼承聚焦 pane 的 cwd,但新 tab 退回 workspace 的 cwd(通常是$HOME)。已驗證:每個非首 tab 的 root panecwd = $HOME。 - workspace(space)label 自動跟隨 root/primary pane 的即時 cwd basename(例如 →
chezmoi、trading-journal)。在 tab 1 裡cd會改 space 名;在其他 tab 裡cd不會。沒有 config 旋鈕控制這個。
new_cwd 值([terminal])—— 沒給明確 --cwd 時,新 pane/tab/workspace 的 CWD 政策:
| 值 | 意義 |
|---|---|
follow(預設) |
繼承來源 pane/workspace(split → 聚焦 pane 的即時 cwd;新 tab → workspace 根) |
home |
$HOME |
current |
herdr 自身行程的目錄(不是聚焦 pane) |
"~/path" |
固定路徑 |
沒有任何 new_cwd 值能讓新 tab 開在聚焦 pane 的即時 cwd——只有明確的 herdr tab create --cwd … 可以(已驗證:tab create --cwd PATH 會把新 tab 的 shell spawn 在 PATH)。要一個「在此開新 tab」的鍵,綁一個 command pane 到 herdr tab create --cwd "$HERDR_ACTIVE_PANE_CWD" --focus(例如綁 prefix+C;原生 prefix+c 保留 = workspace 根):
[[keys.command]]
key = "prefix+C"
type = "pane"
command = "herdr tab create --cwd \"$HERDR_ACTIVE_PANE_CWD\" --focus"
description = "在目前 pane 的 cwd 開新 tab"
可行性對照表 (Feasibility matrix)(現有 tmux 體驗 → herdr)¶
| 現有能力 | herdr 做法 | 這裡怎麼處理 |
|---|---|---|
| Catppuccin 主題 + 明暗 | 原生 [theme] + auto_switch |
在 config.toml 設定 |
| Splits / zoom / 開新 tab+workspace / pane 導覽 | 原生 [keys] actions |
重綁成 tmux 肌肉記憶 |
| Session 持久化(resurrect/continuum) | 原生 detach/reattach | 略過——原生 |
| 滑鼠 / 右鍵選單 | 原生 mouse-first | 略過——原生 |
| Agent 狀態 🤖/💬/✅(workmux,6 檔案) | 原生 側欄 agent-state 彙整 | 略過——原生(tmux 端 workmux 不動) |
sesh 模糊切換 + tmuxp layout |
Plugin herdr-plus Projects + Quick Actions | Plugin + Projects 範本 |
tv channel 彈窗(prefix+T/U/a) |
自訂 command pane([[keys.command]] type="pane") |
Key bindings + 2 個 herdr-aware channel |
| lazygit / scratch 彈窗 | 自訂 command pane | Key bindings |
無縫 Ctrl-hjkl nvim↔pane 導覽 |
沒有 herdr-aware smart-splits | 缺口——見下方 workaround |
OSC133 copy-mode(cpout/cpblock) |
tmux 專屬 | 缺口——cpcmd(zsh history)仍可用 |
| 每視窗狀態符號 + 書籤 ⭐📌 | 部分——report-metadata --custom-status(逐 pane、與 agent 狀態正交) |
待 review 旗標(hmark/prefix+m + tv herdr-review 收件匣);純裝飾的狀態列符號仍是缺口(無 format-string 插值) |
| AI session-summary / agent-wakeup 擷取 | 可改用 herdr pane read / pane list --json 重寫 |
延後——超出試用範圍 |
快捷鍵 (Keybindings)¶
Prefix 是 ctrl+b(跟 tmux 一樣)。內建 action 只能重綁 (rebind)(herdr 的 action 集合是固定的);其餘一切都是 [[keys.command]] 自訂指令。自訂指令會收到 $HERDR_SOCKET_PATH、$HERDR_ACTIVE_PANE_ID、$HERDR_ACTIVE_PANE_CWD,並在聚焦 pane 的 cwd 下執行。
兩組 herdr 環境變數。 上面那組
HERDR_ACTIVE_PANE_*只會被注入到[[keys.command]]的呼叫裡。而每個跑在 herdr pane 內的 shell 還會拿到一組環境值 (ambient):HERDR_ENV=1、HERDR_PANE_ID、HERDR_TAB_ID、HERDR_WORKSPACE_ID、以及HERDR_SOCKET_PATH(當前 session 的 socket)。腳本用HERDR_ENV當作「我在不在 herdr 裡?」的判斷,並繼承HERDR_SOCKET_PATH來鎖定當前 session——這正是hvibe/hcode依賴的東西。
| Key | Action | 類型 |
|---|---|---|
prefix + c / prefix + 1..9 |
新 tab / 切 tab | built-in default |
prefix + h/j/k/l |
聚焦 pane | built-in default |
prefix + \| / prefix + % · prefix + minus / prefix + " |
左右分割 / 上下分割(tmux 肌肉記憶——直覺鍵與 tmux 預設鍵都綁) | rebound(陣列 arrays) |
prefix + z / prefix + x |
zoom / 關 pane | built-in default |
prefix + w / prefix + g |
workspace 導覽(navigate-mode:j/k 或方向鍵移動,Enter 選定) / session navigator |
built-in;navigate_workspace_* 重綁成 j/k + 方向鍵 |
prefix + ctrl + 1..9 |
直接跳到 workspace N(switch_workspace) |
rebound (indexed) |
prefix + alt + 1..9 |
直接跳到 agent N 的 pane(focus_agent) |
rebound (indexed) |
索引跳轉為何用 ctrl/alt 而非 shift:herdr 使用 kitty keyboard protocol,
shift+1仍帶著可列印的!,所以 herdr 比對到的是符號而非shift+1——prefix+shift+1..9會靜默失效。ctrl+digit/alt+digit沒有可列印形式,能乾淨地區分。 |prefix + [| vi copy mode(hjkl、w/b/e、{/}、v、y) | built-in default | |prefix + q| detach | built-in default | |prefix + ?| 快捷鍵說明覆蓋層——列出每個當前綁定與標籤(herdr 原生 which-key;手動觸發,非逾時自動提示) | built-in default | |prefix + ,| 重新命名 tab | rebound(tmux 肌肉記憶) | |prefix + shift + r| reload config(prefix + r保留給 resize mode) | rebound | |prefix + shift + b| 新 git worktree(從prefix + shift + g移過來) | rebound | |prefix + G| lazygit | command pane | |prefix + U|tv tools(CLI launcher) | command pane | |prefix + T|tv herdr-sesh(workspace/dir 切換) | command pane | |prefix + a|tv herdr-agent-panes(即時 agent panes) | command pane | |prefix + f|tv fleet-hosts(SSH picker) | command pane | |prefix + m| 切換目前 pane 的待 review 旗標(⭐) | command pane | |prefix + i|tv herdr-review——待 review 收件匣(被標記的 pane) | command pane | |prefix + `| scratch shell | command pane | |prefix + O| herdr-plus Projects(layout launcher) | plugin action | |prefix + y| herdr-plus Quick Actions | plugin action |大寫字母會解析成
prefix+shift+<letter>,herdr 保留給內建(shift+gworktree、shift+trename-tab、shift+h/j/k/lswap-pane)。prefix+G/prefix+T由上面的重綁釋放出來;herdr server reload-config會在它的diagnostics回報任何殘餘衝突。
Session 輔助函式:hvibe / hcode / hhere / hroot(svibe / scode / shere / sroot 的 herdr 版)¶
dot_config/shell/24_herdr.sh 裡有四個 shell 函式,就是 tmux 的 svibe / scode / shere / sroot 在 herdr 端的對應物。兩個重量級的(hvibe / hcode)能一氣呵成拉起整個 coding workspace;兩個輕量級的(hhere / hroot)只是在某個目錄開一個純 workspace 並 attach——不需要 git repo,也沒有 agent layout。它們呼叫原生的 herdr workspace|tab|pane CLI,並原封不動重用純邏輯(specstory 包裝、--on-exit shell|kill|restart、git-root 解析、agent-CLI 偵測),這些來自 22_sesh.sh;只有 layout 呼叫不同。需要 herdr server 正在跑 + jq。
| 指令 | 建立的 workspace | Layout |
|---|---|---|
hvibe [N] [CLI] / hvibe --agents claude,codex,opencode |
vibe/<repo> |
tab agents(N 個等寬 agent pane)+ tab git(lazygit)+ tab edit(nvim) |
hvibe --tab-per-agent … |
vibe/<repo> |
每個 agent 一個 tab + git + edit tab |
hcode [CLI] |
coding-agent/<repo> |
tab editor(nvim 75% | agent 25%)+ tab monitor(btop) |
hhere [CMD...] |
<basename $PWD> |
單一 tab,$PWD(或 -p DIR)的純 shell;CMD 選用,會在 root pane 執行 |
hroot [CMD...] |
<basename git-root> |
同 hhere,但落在當前 git root(不在 repo 時退回 $PWD) |
- 純開 pair(
hhere/hroot) 補上了「其他每個 herdr 入口都強制要 git repo + 完整 agent layout」的缺口。tmux 的tmux new-session會直接把你丟進$PWD;herdr 多了一層 Workspace,所以少了這對指令,你得先啟動 herdr、建一個 space(會依new_cwd開在$HOME)、再手動cd。hhere一步到位:herdr workspace create --cwd "$PWD"→ focus → 在外部就 attach。不需要 git;選用的指令以原始方式執行(不做 specstory/on-exit 包裝——那留給hcode/hvibe)。flag 見hhere -h/hroot -h。Label 注意事項:tab 1 裡cd之後,herdr 會把 workspace 重新命名為 root pane 的當前 cwd basename(見上方 cwd & workspace-naming model 一節),所以冪等聚焦是盡力而為——label 漂移後重跑會開一個新的 workspace。 - 每 repo 冪等 (idempotent):重跑會聚焦既有的
vibe/<repo>/coding-agent/<repo>workspace,而不是重複建立(對應 svibe 的「存在就 attach」)。 - Attach 感知(像 svibe 的
$TMUX分支):從 herdr 內部跑 → workspace/tab 的 focus 呼叫會切換活著的 client(不開新 client)。從 herdr 外部的一般終端跑 → 輔助函式會拉起一個 attach 到該 session 的 client,讓新 workspace 真的看得到(herdr 的workspace focus只會移動已經 attach 的 client,所以少了這步,整包會被建在看不見的地方)。--no-attach兩種情況都以 detach 方式建立。判斷依據是環境值HERDR_ENV。 --session NAME鎖定某個正在跑的 herdr session(見 具名 session)。預設值:在 herdr 內部時用當前 session(透過繼承的HERDR_SOCKET_PATH),否則用 default session。這個覆寫用local -x HERDR_SOCKET_PATH限縮在函式範圍,所以永遠不會外洩到你的 shell。指定一個沒在跑的--session會報錯,並提示用herdr --session NAME先啟動(hvibe 不會自己 spawn server)。- Agent 可見性:herdr 逐 pane 追蹤 agent 狀態,所以在預設的 splits layout 下,每個 agent 仍會在 herdr 的 agent 追蹤裡各自現身(已驗證:同一 tab 裡兩個 agent 會在
herdr agent list顯示為兩筆)。緊湊的左側欄會把一個 tab 的狀態彙整成一個點——若你要每個 agent 各擁有一個 tab 層級的狀態點,用--tab-per-agent。 - 等寬欄位:herdr 沒有
select-layout even-horizontal,所以hvibe明確設定每次分割的--ratio(1/(N-m+1))以維持 agent pane 等寬;hcode用--ratio 0.75給 nvim 75%。 - 與 svibe/scode 相同的 flag:
--on-exit、--no-specstory、--no-attach、-p/--path,外加--session NAME;hvibe另有--min-width/--tab-per-agent,並吃$HVIBE_MIN_WIDTH/$HVIBE_LAUNCH_STAGGER。完整 flag 說明:hvibe -h/hcode -h。
tmux 的 svibe / scode 仍是 tmux 端的對應物(見 sesh);兩個家族共存——hvibe/hcode 只碰 herdr,svibe/scode 只碰 tmux。
herdr-plus plugin(sesh + tmuxp + menu 的對應物)¶
herdr-plus 加了 Projects(宣告式的多 tab/多 pane workspace 範本 + 模糊選擇器——tmuxp/tmuxinator 的對應物)與 Quick Actions(模糊指令 launcher——彈出選單的對應物)。
安裝已自動化,由 devtools ansible role(# --- herdr-plus plugin --- 區塊)處理——冪等、每次 chezmoi apply 都會跑、裝好後就跳過。它呼叫:
herdr plugin install 在沒有 Go toolchain 時會下載預編譯 release binary,所以有沒有 Go 都能用。唯一的陷阱:若 PATH 上有一個過期的 Go(例如舊的 /usr/local/go 遮蔽了較新的),herdr 會嘗試從原始碼建置並失敗(invalid go version … must match format 1.23),而不是 fallback。ansible task 透過在可用時把 mise 的 Go(mise which go → 它的 bin 目錄)前置到 PATH 來規避;手動修法:把現代 Go 放前面:PATH="$(dirname "$(mise which go)"):$PATH" herdr plugin install cloudmanic/herdr-plus。(Go 現在由 mise 管理,受 installExtraRuntimes gate。)
Projects 範本由 chezmoi 管理於 dot_config/herdr/plugins/config/cloudmanic.herdr-plus/projects/ → ~/.config/ 下的同一路徑。內建的 chezmoi.toml 對應本 repo 的 tmuxinator chezmoi session(editor/git/shell tab)。把 prefix+O 綁到 Projects、prefix+y 綁到 Quick Actions(設定裡已備好)。
Television 整合¶
大多數 tv channel(tools、fleet-hosts、mlflow、kill-process、ssh-config)的 action 不與 tmux 耦合,所以在 herdr command pane 裡原樣就能跑——綁一個鍵到 tv <channel> 即可。只有那些 action 會呼叫 tmux … 的 channel 才需要 herdr-aware 變體。這裡出貨三個:
herdr-sesh(dot_config/television/cable/herdr-sesh.toml)—— 列出 herdr session/workspace + zoxide 目錄;Enter 會分派herdr session attach/herdr workspace focus/herdr workspace create --cwd,而不是sesh connect/tmux switch-client。herdr-agent-panes(dot_config/television/cable/herdr-agent-panes.toml)—— 與agent-panes同來源,但切換/kill 改用herdr pane focus/herdr pane close。herdr-review(dot_config/television/cable/herdr-review.toml)—— 待 review 收件匣:只列出帶 ⭐ 旗標的 pane(custom_status ~ REVIEW)。Enter 會 focus 該 pane 的 workspace/tab 並保留旗標;Alt+C則 focus 並清除旗標(「mark read」)。綁在prefix+i。見下方 待 review 旗標 一節。
原本綁 tmux 的 sesh / agent-panes channel 保持不變以利共存。
Agent 狀態(取代 6 檔案的 workmux 整合)¶
herdr 原生偵測 agent 狀態並彙整進側欄(一個 blocked 的 agent 會標記它的 pane/tab/workspace)。Claude Code 是靠 screen-manifest 啟發式 (heuristics)(終端標題 + OSC progress)偵測,而非生命週期 hook。若啟發式不夠用,可以明確推送狀態:
試用期間我們仰賴原生偵測——tmux 端的 workmux 🤖/💬/✅ 系統(Claude/OpenCode hook → @workmux_status)不動,且只在 tmux 下生效。
可選的 agent 整合(onboarding 的「install」按鈕)¶
herdr 首次執行的 onboarding 會提議安裝可選的 agent 整合(herdr integration install <agent>),讓 agent 直接回報狀態,而不必依賴 screen 啟發式。按下 install 會為每個偵測到的 agent 都設好。它會寫入什麼(本機已驗證):
| Agent | herdr integration install 建立什麼 |
會碰到 repo 管理的檔案嗎? |
|---|---|---|
| claude | ~/.claude/hooks/herdr-agent-state.sh + ~/.claude/settings.json 裡一個 hook 項目 |
會——但 repo 的 hook-aware modify_settings.json merger 會保留它(跟它對待 CodeIsland 一樣)。chezmoi apply 是 no-op;不會把 herdr hook 拔掉。 |
| codex | 只有 ~/.codex/herdr-agent-state.sh |
不會——~/.codex/config.toml 不動(與 chezmoi 計算出的目標相同)。 |
| opencode | ~/.config/opencode/plugins/herdr-agent-state.js(獨立 plugin) |
不會——只有 workmux-status.ts 受管;herdr 的 plugin 共存。 |
| cursor | ~/.cursor/herdr-agent-state.sh + hook |
腳本在 chezmoi 之外;共存。 |
這些整合檔案未被 vendored 進 repo,所以不會在其他機器上重現(在那些機器再按一次 install,或跳過 onboarding)。它們用 herdr 自己的 socket,不會干擾 tmux/workmux(不同機制)。移除方式:herdr integration uninstall <agent>——而對 claude,之後要再跑一次 chezmoi apply,讓 merger 從 settings.json 丟掉那個已移除的 hook。
設定管理(為何用 modify_)¶
herdr 會在執行期改寫 ~/.config/herdr/config.toml——完成 onboarding 會在檔首插入 onboarding = false,而 app 內的設定彈窗(theme / sound / toasts / pane labels)在套用時也會持久化到那裡。它就地編輯並保留既有註解,但執行期它擁有這個檔案。
這個檔案原本用 create_ 前綴只植入一次,避免 chezmoi apply 蓋掉那些寫回。代價是:repo 的編輯永遠傳不到已植入的機器——你在 source 改的分割鍵重綁、註解修正,都得手動 cp … source-path 刷新才會抵達,否則悄悄不見。這正是為何一台機器在 chezmoi diff 顯示乾淨時仍會感覺「沒同步」(diff 之所以乾淨,正是因為 create_ 從不再碰該檔)。這與跨機器無關:source 對每台 live 檔的 diff 可以逐字節相同,但 repo 編輯在你刷新前仍不會落地。
現在改成 modify_ 覆蓋層——dot_config/herdr/modify_config.toml.tmpl,一個小腳本:
- 以受管本體
.chezmoitemplates/herdr/config.toml為 base(帶註解 + tmux-parity 說明),每次 apply 強制套用[theme]/[ui]/[terminal]/[keys]表,並 - 原樣拉回 (pull through) live 檔的其他每個頂層 key(
onboarding、[session]、[remote]、[update]、[experimental]…),讓 herdr 的執行期寫回存活。
TOML 沒有 jq,所以合併用 Python 透過 uv run --with tomlkit 跑(tomlkit 能來回保留註解與 [[keys.command]] 的 array-of-tables;stdlib tomllib 唯讀,而 codex 的 modify_ emitter 無法輸出 AoT)。它退化到系統 python3,再退化到直接輸出原始受管範本,所以沒有 Python 的全新主機仍拿到完整設定。這是繼 ~/.codex/config.toml(dot_codex/modify_config.toml.tmpl)之後第二個 TOML-overlay 先例。
要改 herdr 的受管設定,編輯 .chezmoitemplates/herdr/config.toml 再 chezmoi apply——它現在會抵達每台主機。用 herdr server reload-config 驗證(會在 diagnostics 回報快捷鍵衝突;空的 diagnostics 陣列 + "status":"applied" 代表設定——含陣列鍵綁定——解析乾淨)。
持久化與還原(不小心關掉)¶
兩個原生層,不需要 plugin:
- 關 client/終端 ≠ 丟失 session。 herdr 跑一個持久化的 server(具名 session
default,socket~/.config/herdr/herdr.sock)。關終端視窗只是 detach——workspace/tab/pane 及其執行中的行程都還活著。用裸herdr(或herdr session attach default)重新 attach;herdr session list會顯示它running。這就是 tmux 的 detach/reattach,內建。 - 完整 server 重啟 / 重開機 / crash。 設定裡的
[session]有resume_agents_on_restore = true——在 server 重啟後把支援的 AI-agent pane 恢復到它們原生的對話 session(需要官方herdr integration install <agent>hook)——外加一個選項可跨重啟保存最近的 pane 畫面歷史。這原生涵蓋了 tmux-resurrect/continuum 的情境,受那些 key + 整合 gate。
遠端 session (herdr --remote)¶
herdr 能跑一個透過 SSH attach 到另一台主機上 herdr server 的本機 thin client:
herdr --remote <ssh-target> [--session NAME] [--handoff]
herdr --remote local_ubuntu # <ssh-target> 是 ~/.ssh/config 裡任何主機/別名
它做什麼(依 https://herdr.dev/docs/remote):
- SSH 到
<ssh-target>,偵測遠端平台,並在那裡自動安裝對應的 herdr server binary——遠端不需要預先裝好 herdr(但需要 SSH 存取權 + 安裝/執行 binary 的權限)。 - 把你的本機剪貼簿(含貼圖)橋接到遠端 server,並沿用你的本機快捷鍵(
--remote-keybindings server可改用 server 的)。 --session NAME在遠端挑選/建立一個具名 session;--handoff選擇加入 live handoff。
設定——config.toml 的 [remote] 區塊:
manage_ssh_config = true(預設)—— herdr 透過一份產生的設定跑橋接 SSH,該設定先 include 你的~/.ssh/config,再補上ServerAliveInterval/ServerAliveCountMax,讓閒置的 NAT/網路逾時不會斷掉 session。設成false則用原封不動的你自己的設定跑純 SSH。remote_image_paste([keys])—— 原始按鍵的貼圖綁定,只在--remote下生效。
排解 remote platform detection failed: Connection closed by <host> port 22:這是暫時性的 SSH 層級斷線,不是 herdr 設定問題。先確認 herdr 用的 SSH 路徑真的通,然後重試即可:
若純 SSH 通,就重試 herdr --remote <ssh-target>;若一直失敗,在失敗當下看 ~/.config/herdr/herdr-client.log / herdr.log。常見的真正原因:SSH 認證需要橋接無法回答的互動提示,或 sshd 對快速連線做速率限制(MaxStartups)。
在 tmux 內巢狀執行 herdr(多 remote)¶
本 repo 的預設立場是 herdr 或 tmux,不巢狀。但你可以刻意巢狀——tmux 當外層視窗管理器,內含數個 herdr client,有些本地、有些 herdr --remote <host> 連到不同 server。癥結是 prefix 衝突:兩者都預設 Ctrl+b,所以外層 tmux 吃掉它,內層 herdr 永遠收不到自己的 prefix(herdr #759)。
三種解法,依這裡的偏好排序:
- 雙擊 prefix 轉發(推薦,零設定)。 tmux 保有預設的
bind -T prefix C-b send-prefix(已確認本 repo 未覆寫),所以Ctrl+b Ctrl+b會把一個字面Ctrl+b轉發給內層 herdr;之後它的 prefix 綁定正常運作(Ctrl+b Ctrl+b c= 內層開新 tab)。herdr 與 tmux 都不用改——經典的 tmux-in-tmux 肌肉記憶。 - 改內層 herdr 的 prefix。 設
keys.prefix(例如"ctrl+a"/"ctrl+space")讓內層 herdr 用不衝突的 prefix——一次按鍵而非雙擊。代價:即使單獨跑 herdr,prefix 也變了(不再與 tmux 的Ctrl+b一致)。 - 不巢狀——用 herdr 原生遠端。
herdr --remote <target> [--remote-keybindings local|server]跑一個本機 thin client 連到遠端 herdr server,中間沒有 tmux,而 herdr 自己就託管多個具名 session。對純粹「多個 remote」的需求,這通常比巢狀乾淨;--remote-keybindings local(預設)沿用你的本機 keymap,server用遠端的。
注意:本 repo 的 tmux 用了很多 root-table bind -n C-* 綁定會遮蔽內層 app 的 Ctrl 鍵,所以 herdr 的 prefix-free direct 終端捷徑在 tmux 下不可靠——透過它的 prefix 觸及 herdr action(用 1 或 2)。herdr 自己的 allow_nested(config,預設 false)只管 herdr-inside-herdr(靠 HERDR_ENV 偵測),不是 herdr-inside-tmux。
待 review 旗標(mark-unread / ⭐)¶
tmux 那邊有逐視窗書籤(@bookmark_status + toggle-bookmark.sh,靠 #{?@bookmark_status,…} 渲染)。herdr 沒有 #{@option} 狀態列插值,所以那套機制不能直接移植——但這裡真正重要的用途可以:「agent 跑完了(done)但我還沒 review」。herdr 一旦你點進去瞄一眼就把 done pane 塌成 idle,唯一還需要注意的訊號就此消失。
解法用 herdr 的逐 pane custom-status metadata,它與原生 agent 偵測正交:
herdr pane report-metadata <pane> --source review --custom-status "⭐ REVIEW" # 設定(永久——不帶 --ttl-ms)
herdr pane report-metadata <pane> --source review --clear-custom-status # 清除
已驗證:一個 pane 可以同時帶 custom_status:"⭐ REVIEW" 與 agent_status:"idle"——所以點進去不會清掉旗標(重點所在)。herdr pane get 會吐出 custom_status 欄位,herdr pane list(原生 JSON——不加 --json flag)讓收件匣列舉被標記的 pane,所以不需要 sidecar 檔;herdr 自己就是真相源。
各介面(共用同一支腳本 ~/.config/herdr/review-mark.sh = dot_config/herdr/executable_review-mark.sh,即 tmux toggle-bookmark.sh 的對應物):
| 介面 | 動作 |
|---|---|
hmark / hunmark(dot_config/shell/24_herdr.sh 的 alias) |
對目前 pane(環境值 $HERDR_PANE_ID)set / clear,或傳入 pane id |
prefix+m |
切換 focus pane 的旗標(用 $HERDR_ACTIVE_PANE_ID) |
tv herdr-review / prefix+i |
收件匣:只列出被標記的 pane。Enter focus 過去並保留 ⭐(你可能還在 review 中);Alt+C focus 並清除(「mark read」) |
Caveat——custom_status 每 pane 單值。 延後中的用量狀態 driver(下方)也會推 --custom-status;雖然用不同 --source,herdr pane get 只吐一個扁平的 custom_status,兩者會爭同一個可見標籤。目前無實際衝突(driver 未建、review 旗標短暫),但見 backlog/herdr-usage-status-driver.md。
AI 用量 / 額度狀態¶
herdr 沒有原生的用量/額度/token 顯示(側欄只顯示 agent 狀態)。它確實有一個逐 pane 的 hook——herdr pane report-metadata <pane> --source ID --custom-status "…" --ttl-ms N——driver 可以把 "Claude 62% • Codex 78%" 之類的標籤推進去(與上方 待 review 旗標 用的是同一個 hook——注意兩者都寫入單一的逐 pane custom_status)。有一個 Codex-only 的社群 plugin(jerryfane/herdr-codex-usage-kit)已從 CodexBar 讀的同一份 ~/.codex 資料做到這件事;但沒有東西涵蓋 Claude/ChatGPT 額度。延後——CodexBar 的選單列仍是多供應商的檢視。設計與選項記在 backlog/herdr-usage-status-driver.md。
缺口(沒有乾淨的 herdr 對應)¶
- 無縫
Ctrl-hjklnvim↔pane 導覽。vim-tmux-navigator與 tmux 耦合(is_vim的ps/pane_tty啟發式 + nvim plugin)。herdr 沒有 smart-splits 對應物——它的 pane focus 是prefix+h/j/k/l,在邊界不會穿透進 nvim 的 splits。Workaround:在 nvim 裡用它自己的<C-w>hjkl。這是相對 tmux 最大的 UX 退步。 - OSC133 copy-mode(
cpout/cpblock、prompt 跳轉、最後輸出 yank)是 tmux 專屬。herdr 的 copy mode(prefix+[)是 vi 風格但沒有 OSC133 的 prompt 邊界感知。cpcmd(zsh history,與多工器無關)仍可用。 - 裝飾用的狀態列符號(📌/🔖 當作自由浮動的視窗標籤):herdr 沒有
#{@option}的 format-string 插值,所以 tmux 風格的狀態列書籤不能移植。(但「mark-unread / 待 review ⭐」這個具體用途已解決——見上方 待 review 旗標 一節——靠逐 pane 的custom_statusmetadata;只剩純裝飾的狀態列符號仍是缺口。) - 逐鍵 pane 縮放。 tmux 把
prefix+H/J/K/L(與M-hjkl微調)直接綁成縮放;herdr 沒有逐鍵縮放——它用模態的resize_mode(prefix+r)再按h/j/k/l。不是精確對等,但是相近的對應物。
不是缺口: vi copy-mode 本身是原生(
prefix+[),且逐 pane 的 agent 狀態是原生偵測——我原本以為會缺的兩件事,結果都內建了。