--- name: resolve_pane_ids kind: function lang: go domain: infra version: "1.0.0" purity: impure signature: "func ResolvePaneIDs(socket string, claudePIDs []int) map[int]string" description: "Resuelve el pane_id (\"%N\") de tmux de cada proceso dado en un socket aislado (tmux -L ), devolviendo un mapa claudePID -> pane_id. Lista los panes con `list-panes -a -F '#{pane_pid} #{pane_id}'` y, para cada PID, sube por el arbol de procesos (PPID en /proc//stat) hasta dar con un pane_pid del socket; ese pane es el que aloja al proceso (normalmente pane_pid == PID porque el pane corre `exec claude`, pero un shell que lanzo el proceso como hijo se cubre con el ascenso). El pane_id es estable de por vida del pane, inmune a los swaps de window que mueve el focus de la flota, por eso es el identificador correcto de un agente frente al window_id (@N). Best-effort: socket vacio, sin PIDs o fallo de tmux -> mapa vacio; un PID sin pane resoluble se omite. Capa de control tmux de fleetview / orquestador." tags: [claude-fleet, infra, tmux, pane, fleet, orchestration] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [] params: - name: "socket" desc: "Nombre del socket tmux aislado (tmux -L ). En la flota suele ser 'fleet'/'fleet3' ($FLEET_SOCKET). Escanea TODOS los panes del servidor de ese socket (list-panes -a). \"\" -> mapa vacio." - name: "claudePIDs" desc: "PIDs de los procesos (normalmente claude) cuyo pane_id se quiere resolver. Vacio/nil -> mapa vacio sin llamar a tmux." output: "map[int]string con clave = PID de entrada y valor = pane_id ('%N') del pane que lo aloja. Un PID sin pane resoluble en su ascendencia se omite (el caller lo degrada a \"\"). Mapa vacio (sin panic, sin error) si socket viene vacio, claudePIDs viene vacio, o `tmux list-panes -a` falla (socket caido, tmux ausente)." tested: true tests: ["TestResolvePaneIDsFrom", "TestResolvePaneIDsFromUnresolvable", "TestResolvePaneIDsFromMalformedLines", "TestResolvePaneIDsEmptyInputs", "TestPaneAncestorBounded"] test_file_path: "functions/infra/resolve_pane_ids_test.go" file_path: "functions/infra/resolve_pane_ids.go" notes: "Build tag //go:build !windows (depende de /proc y de tmux, no portable a Windows). Comparte runTmux (tmux_new_claude_window) y procPPID (tmux_map_claude_panes) con el resto de la capa tmux del paquete infra. El nucleo resolvePaneIDsFrom(tmuxOut, ppidOf, pids) es testeable inyectando la salida de tmux y el resolvedor de PPID, sin procesos reales. El ascenso por el arbol esta acotado (64 saltos, corte en pid<=1) para no colgarse ante un /proc malformado o un ciclo. Hermana de tmux_map_claude_panes_go_infra: aquella mapea PID -> window_id (@N, posicion operativa que migra con el swap); esta mapea PID -> pane_id (%N, identidad estable). Pensada para que list_claude_fleet_go_infra (y consumidores como el orquestador) identifiquen a cada agente por un handle que no baila al hacer focus." --- ## Ejemplo ```go package main import ( "fmt" "fn-registry/functions/infra" ) func main() { // Resolver el pane_id estable de un par de procesos claude en el socket fleet. byPID := infra.ResolvePaneIDs("fleet", []int{3637133, 3640001}) for pid, paneID := range byPID { fmt.Printf("pid=%d -> pane=%s\n", pid, paneID) // ej. pid=3637133 -> pane=%8 } // Un PID sin pane resoluble simplemente no aparece en el mapa. } ``` ```go // Patron tipico: cruzar la flota con su pane_id estable. fleet, _ := infra.ListClaudeFleet() pids := make([]int, 0, len(fleet)) for _, c := range fleet { pids = append(pids, c.PID) } panes := infra.ResolvePaneIDs("fleet", pids) // map[pid]"%N" ``` ## Cuando usarla Cuando necesites un identificador ESTABLE de un agente de la flota a partir de su PID: el pane_id ("%N") de tmux no cambia durante toda la vida del pane, aunque el pane migre de window al hacer focus (break-pane + join-pane). Usala en vez de referirte al window_id (`@N`, `TmuxWindow`), que baila cada vez que el agente entra/sale de la console. La consume `list_claude_fleet_go_infra` para poblar `ClaudeFleet.PaneID`, y el orquestador para referirse a un ejecutor por un handle que no se confunde de agente. ## Gotchas - **Impura: ejecuta `tmux` y lee `/proc`.** No es determinista entre llamadas (la flota cambia). Solo lectura — no mueve ni mata panes. - **Best-effort, nunca crashea.** Socket vacio, lista de PIDs vacia, o un `tmux list-panes -a` que falla (socket caido, tmux no instalado) devuelven un mapa vacio. Un agente sin pane resoluble (proceso huerfano, pane cerrado) se omite del mapa; el caller lo degrada a "". - **Sube por el arbol de procesos.** Si el pane corre un shell que lanzo claude como hijo (en vez de `exec claude`), el pane_pid no es el claude PID; el ascenso por PPID lo cubre. El ascenso esta acotado a 64 saltos (corte en pid<=1) para no colgarse ante un ciclo o un /proc raro. - **Parseo del `comm` en /proc.** El PPID se saca de `/proc//stat` tomando lo que hay tras el ULTIMO ')' (el comm va entre parentesis y puede contener espacios y ')'). Reutiliza el `procPPID` robusto del paquete. - **/proc + tmux no portables.** Build tag `//go:build !windows`; depende de `/proc//stat` (Linux) y del binario `tmux`. - **Opera SIEMPRE sobre el socket aislado** (`tmux -L `), escaneando todos sus panes con `list-panes -a`. No mira el servidor tmux por defecto del usuario.