feat(infra): grupo claude-fleet — FleetView TUI + orquestacion de Claudes
Sistema FleetView para centralizar la flota de procesos Claude Code vivos en una sola ventana kitty + tmux (socket aislado -L fleet) con un panel TUI: - list_claude_fleet (+ tipo claude_fleet): escanea ~/.claude/sessions + goals + runtime, valida procesos vivos (anti-PID-reciclado), join por sessionId. - list_resumable_claudes (+ tipo resumable_claude): sesiones cerradas reanudables. - wrappers tmux: tmux_new_claude_window (con --resume), tmux_swap_window_into_console (preserva ancho del sidebar), tmux_map_claude_panes. - launch_kittyclaude: comando entrypoint; instala atajos alt+flechas/enter/n/0/k/r, mouse on, remain-on-exit off; fija el ancho del sidebar con hooks. - docs/capabilities/claude-fleet.md + entrada en el INDEX. Incluye ademas funciones datascience en progreso (excel/duckdb/postgres) y ajustes varios de docs e infra de otra sesion, agrupados aqui para no perderlos. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
//go:build !windows && linux
|
||||
|
||||
package infra
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestTmuxMapClaudePanesNoClaude verifica que, sobre un servidor tmux aislado
|
||||
// cuyos panes solo corren `cat` (no claude), el mapa devuelto esta vacio: ningun
|
||||
// pane es ni tiene como hijo un proceso `claude`. Tambien valida que el comando
|
||||
// list-panes -a se ejecuta sin error sobre el socket aislado.
|
||||
func TestTmuxMapClaudePanesNoClaude(t *testing.T) {
|
||||
tmuxAvailable(t)
|
||||
socket := isolatedSocket(t)
|
||||
session := "fleet"
|
||||
startConsoleSession(t, socket, session)
|
||||
newCatWindow(t, socket, session)
|
||||
newCatWindow(t, socket, session)
|
||||
|
||||
m, err := TmuxMapClaudePanes(socket)
|
||||
if err != nil {
|
||||
t.Fatalf("TmuxMapClaudePanes: %v", err)
|
||||
}
|
||||
if len(m) != 0 {
|
||||
t.Errorf("ningun pane corre claude, el mapa deberia estar vacio, tiene %d: %v", len(m), m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTmuxMapClaudePanesEmptySocket(t *testing.T) {
|
||||
if _, err := TmuxMapClaudePanes(""); err == nil {
|
||||
t.Error("socket vacio deberia dar error")
|
||||
}
|
||||
}
|
||||
|
||||
// TestProcCommSelf valida procComm contra el propio proceso de test: comm debe
|
||||
// coincidir con el de /proc/self/comm (el binario de test, no "claude").
|
||||
func TestProcCommSelf(t *testing.T) {
|
||||
self := os.Getpid()
|
||||
got := procComm(self)
|
||||
if got == "" {
|
||||
t.Fatalf("procComm(%d) devolvio vacio", self)
|
||||
}
|
||||
want := strings.TrimSpace(readSelfComm(t))
|
||||
if got != want {
|
||||
t.Errorf("procComm(%d) = %q, /proc/self/comm = %q", self, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func readSelfComm(t *testing.T) string {
|
||||
t.Helper()
|
||||
data, err := os.ReadFile("/proc/self/comm")
|
||||
if err != nil {
|
||||
t.Fatalf("read /proc/self/comm: %v", err)
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
// TestFindClaudePIDDetectsChild ejercita el mecanismo "¿este pid o hijo es
|
||||
// claude?" SIN claude real: lanza un proceso hijo cuyo comm sea verificable y
|
||||
// comprueba que (a) findClaudePID(propio pid) no lo confunde con claude, y (b)
|
||||
// procChildren detecta al hijo lanzado. Testear con un proceso `claude` real es
|
||||
// inviable en CI; este test valida el helper de deteccion con un comm conocido.
|
||||
func TestFindClaudePIDDetectsChild(t *testing.T) {
|
||||
// (a) El proceso de test NO es claude: findClaudePID no debe reportarlo.
|
||||
if _, ok := findClaudePID(os.Getpid()); ok {
|
||||
// Solo seria true si el binario de test se llamara "claude" (no es el caso).
|
||||
t.Errorf("findClaudePID(self) reporto claude para un proceso que no lo es")
|
||||
}
|
||||
|
||||
// (b) Lanzamos un hijo `sleep` (comm conocido "sleep") y verificamos que
|
||||
// procChildren lo detecta como descendiente directo. Esto valida el
|
||||
// mecanismo de barrido de hijos que findClaudePID usa internamente para
|
||||
// localizar un comm objetivo (en produccion: "claude").
|
||||
cmd := exec.Command("sleep", "3")
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Skipf("no se pudo lanzar sleep: %v", err)
|
||||
}
|
||||
childPID := cmd.Process.Pid
|
||||
t.Cleanup(func() { _ = cmd.Process.Kill(); _ = cmd.Wait() })
|
||||
|
||||
kids := procChildren(os.Getpid())
|
||||
found := false
|
||||
for _, k := range kids {
|
||||
if k == childPID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("procChildren(self) no incluyo al hijo %d; kids=%v", childPID, kids)
|
||||
}
|
||||
|
||||
// Y el comm del hijo debe ser "sleep", confirmando el camino que findClaudePID
|
||||
// usa para comparar contra "claude".
|
||||
if comm := procComm(childPID); comm != "sleep" {
|
||||
t.Errorf("procComm(%d) = %q, esperado \"sleep\"", childPID, comm)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user