feat(infra): exponer pane_id (%N) estable en el JSON de la flota
El orquestador identificaba cada agente por el campo tmux_window (@N), pero el window_id de tmux cambia cuando un pane entra/sale de windows (el focus de la flota usa break-pane + join-pane, que recrean windows). El pane_id (%N) en cambio es estable durante toda la vida del pane: es el identificador correcto. - claude_fleet.go: nuevo campo ClaudeFleet.PaneID `json:"pane_id"`. Se mantiene TmuxWindow (lo necesita el focus internamente); esto AÑADE pane_id, no lo reemplaza. - resolve_pane_ids.go (+ .md, .go test): nueva función del registry ResolvePaneIDs(socket, pids) -> map[pid]pane_id. Lista los panes del socket (tmux -L <socket> list-panes -a) y para cada PID sube por el árbol de procesos (PPID en /proc) hasta dar con un pane_pid. Reutiliza runTmux y procPPID del paquete infra. Best-effort: tmux/socket caído o PID sin pane -> "" sin crash. Núcleo testeable con inyección de la salida tmux y del resolvedor de PPID. - list_claude_fleet.go: ListClaudeFleet() puebla PaneID resolviendo cada PID vivo contra $FLEET_SOCKET (default "fleet"). Solo la entrada pública lo hace; ListClaudeFleetFrom() queda intacta (cero coste tmux en tests y en el bucle de render de fleetview). Tag de grupo: orchestration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,13 +42,47 @@ type runtimeFile struct {
|
||||
|
||||
// ListClaudeFleet scans the current user's ~/.claude directory and returns the
|
||||
// fleet of Claude Code sessions known to the machine. It is a thin wrapper over
|
||||
// ListClaudeFleetFrom resolving the home directory.
|
||||
// ListClaudeFleetFrom resolving the home directory, plus it populates each
|
||||
// member's PaneID ("%N") by resolving it against the fleet tmux socket.
|
||||
//
|
||||
// The socket comes from $FLEET_SOCKET, defaulting to "fleet". Resolution is
|
||||
// best-effort: if tmux/the socket is unavailable, every PaneID is left "" and
|
||||
// the fleet is still returned. PaneID is only populated here (the public
|
||||
// registry entry point), not in ListClaudeFleetFrom, so consumers that call the
|
||||
// core directly in a hot loop (and the unit tests) pay no tmux cost.
|
||||
func ListClaudeFleet() ([]ClaudeFleet, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve home dir: %w", err)
|
||||
}
|
||||
return ListClaudeFleetFrom(filepath.Join(home, ".claude"))
|
||||
fleet, err := ListClaudeFleetFrom(filepath.Join(home, ".claude"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
populatePaneIDs(fleet)
|
||||
return fleet, nil
|
||||
}
|
||||
|
||||
// populatePaneIDs resolves each alive member's pane_id ("%N") against the fleet
|
||||
// tmux socket ($FLEET_SOCKET, default "fleet") and writes it into PaneID. It
|
||||
// mutates fleet in place. Best-effort: tmux/socket down -> every PaneID stays ""
|
||||
// (ResolvePaneIDs returns an empty map), no crash. Only alive PIDs are queried;
|
||||
// a dead PID has no pane to resolve.
|
||||
func populatePaneIDs(fleet []ClaudeFleet) {
|
||||
socket := os.Getenv("FLEET_SOCKET")
|
||||
if socket == "" {
|
||||
socket = "fleet"
|
||||
}
|
||||
pids := make([]int, 0, len(fleet))
|
||||
for _, f := range fleet {
|
||||
if f.Alive {
|
||||
pids = append(pids, f.PID)
|
||||
}
|
||||
}
|
||||
byPID := ResolvePaneIDs(socket, pids)
|
||||
for i := range fleet {
|
||||
fleet[i].PaneID = byPID[fleet[i].PID]
|
||||
}
|
||||
}
|
||||
|
||||
// ListClaudeFleetFrom scans claudeDir (e.g. ~/.claude) and returns the fleet of
|
||||
|
||||
Reference in New Issue
Block a user