架构
herdr-bar 是两个 Swift target:HerdrCore(协议、发现、聚合、举起宿主)和 MacBar(NSStatusItem + SwiftUI popover)。Herdr.js 仍是状态顺序、计数、socket 路径的行为对照。
分层
从 socket 到菜单栏
MacBarApp 把激活策略设成 .accessory。没有 Dock 图标,没有主窗口。
数据流
事件是失效信号,列表才是真相
每个发现到的 socket 得到一个 SessionWatcher:
- 命令 socket(用完即关):
agent.list、agent.focus、tab.focus、pane.focus - 订阅 socket(长连接):
events.subscribe。无id的行触发刷新
刷新路径:agent.list → GitBranchCache.enrich(读 .git/HEAD,不 spawn git)→ SessionTimeCache.enrich(Claude jsonl)→ SessionSnapshot。失败则指数退避重连(1s → 30s)。
AgentAggregator 只合并 online 快照。session 掉线,它的 agent 从总列表消失。
发现与聚合
SessionDiscovery 扫描:
| Session | Socket |
|---|---|
default | ~/.config/herdr/herdr.sock |
named(如 work) | ~/.config/herdr/sessions/<name>/herdr.sock |
固定 HERDR_SOCKET | 该路径,名字叫 demo |
SessionManager 监视 ~/.config/herdr 和 sessions/(DispatchSource 文件事件)。这是 文件系统失效,不是 agent.list 轮询。配置目录还不存在时,用 2s 退避直到出现。
Agent 身份是 (sessionName, pane_id)。Agent.id 渲染成 sessionName::paneId。同名 pane 可以同时活在 default 和 work 里。
聚焦宿主
FocusRaiser 和协议逻辑隔离。点一行的路径是:
agent.focus— 把该 agent 标成已看tab.focus/pane.focus— Herdr 0.9 起 workspace/tab 是每个客户端自己的;extra 走的是 API socket,只发agent.focus往往切不动已经 attach 的 TUIraiseHost— TUI 切完再激活 GUI 祖先(activateIgnoringOtherApps,不用activateAllWindows)
命名 session 芯片箭头跳过 1–2,只对该 session 调 raiseHost,TUI 保持上一次选中的 pane。
它找的是 TUI 客户端:
herdr(无参数)= default TUIherdr session attach <name>= named session- 不是
herdr server(通常 ppid 为 1,握着监听 socket)
然后沿父进程走到 activationPolicy == .regular 的 GUI 祖先(跳过 Xcode 和 herdr-bar 自己),unhide + activate。macOS 14+ 先 yieldActivation。宿主 PID 缓存在点击路径之外,用 sysctl(KERN_PROC / KERN_PROCARGS2),不 spawn lsof / ps。