feat(orquestador): feed reactivo FLEET-STATE + fix peek de drain_fleet_events

El orquestador no se enteraba de los cambios de estado de su flota: el drenado
era manual y el peek documentado `./fn run drain_fleet_events --advance false`
devolvia un falso `{total_new:0, cursor:0}` porque `fn run` mapea los argumentos
posicionalmente y no parsea flags `--nombre valor` (events_path acababa valiendo
"--advance", una ruta inexistente).

- drain_fleet_events: nuevo helper _normalize_fn_run_flags que renormaliza el
  patron `--advance <bool>` aplanado por `fn run`, de modo que el peek funciona
  directo desde la CLI sin tocar el runner de Go. Bump 1.1.0 + growth log + tests
  del normalizador (unit y end-to-end por HOME).
- summarize_fleet_transitions (nueva, pure, grupo claude-fleet): resume el dict
  by_classification de drain en un bloque de una linea con las tres categorias
  accionables (terminados / reclaman / estancados), dedup por session_id y
  truncado de objetivo.
- hook_fleet_state_inject.sh (UserPromptSubmit): si la sesion es role=orchestrator
  (leido de ~/.claude/goals/<session_id>.json), hace peek de la cola sin mover el
  cursor y emite el bloque FLEET-STATE cada turno. Degrada limpio si el watcher
  esta caido, la cola no existe o la sesion no es orquestador.

El registro del hook va en .claude/settings.local.json (gitignored, fuera de este
commit). Pendiente, lo integra otro agente: documentar el bloque FLEET-STATE en
.claude/commands/orquestador.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
agent
2026-06-21 00:06:01 +02:00
parent b410328cec
commit 118d5d36d3
7 changed files with 548 additions and 2 deletions
@@ -39,6 +39,13 @@ def drain_fleet_events(
numero de lineas de la cola (cola truncada/rotada): se reinicio
desde 0.
"""
# `fn run` aplana los argumentos posicionalmente y NO parsea flags
# `--nombre valor`. Renormalizar el caso del peek documentado antes de
# resolver rutas (ver _normalize_fn_run_flags).
events_path, cursor_path, advance = _normalize_fn_run_flags(
events_path, cursor_path, advance
)
home = os.path.expanduser("~")
if events_path is None:
events_path = os.path.join(home, ".claude", "fleet", "events.jsonl")
@@ -127,3 +134,40 @@ def drain_fleet_events(
if reset:
result["reset"] = True
return result
def _normalize_fn_run_flags(
events_path: str | None,
cursor_path: str | None,
advance: bool,
) -> tuple:
"""Renormaliza el peek `./fn run drain_fleet_events --advance false`.
`fn run` mapea los argumentos de la linea de comandos POSICIONALMENTE a los
parametros de la funcion y NO entiende flags `--nombre valor`. Por eso
invocar `./fn run drain_fleet_events --advance false` asigna
events_path="--advance" y cursor_path="false", lo que rompia el peek
(events_path apuntaba a una ruta inexistente -> drain vacio con cursor 0).
Cuando el primer posicional es el flag `--advance`, este helper lo
interpreta: el segundo posicional (cursor_path) es su valor booleano y las
dos rutas vuelven a su default (None). `--advance false` -> advance=False;
`--advance true` o `--advance` sin valor -> advance=True. Una llamada normal
por kwargs (events_path/cursor_path reales) pasa sin cambios.
Returns:
tupla (events_path, cursor_path, advance) ya normalizada.
"""
if (
isinstance(events_path, str)
and events_path.lstrip("-").replace("-", "_") == "advance"
):
if cursor_path is None:
# `--advance` sin valor: presencia del flag => True (store_true).
advance = True
else:
value = str(cursor_path).strip().lower()
advance = value not in ("false", "0", "no", "off", "")
events_path = None
cursor_path = None
return events_path, cursor_path, advance