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:
@@ -2,7 +2,7 @@
|
||||
|
||||
import json
|
||||
|
||||
from drain_fleet_events import drain_fleet_events
|
||||
from drain_fleet_events import _normalize_fn_run_flags, drain_fleet_events
|
||||
|
||||
|
||||
def _write_queue(path, events):
|
||||
@@ -132,6 +132,57 @@ def test_agrupacion_por_to_y_urgent(tmp_path):
|
||||
assert urgent_sessions == {"a", "d"}
|
||||
|
||||
|
||||
def test_normaliza_flag_advance_false_posicional():
|
||||
# `fn run drain_fleet_events --advance false` aplana a estos posicionales.
|
||||
ev, cur, adv = _normalize_fn_run_flags("--advance", "false", True)
|
||||
assert (ev, cur, adv) == (None, None, False)
|
||||
|
||||
|
||||
def test_normaliza_flag_advance_true_posicional():
|
||||
ev, cur, adv = _normalize_fn_run_flags("--advance", "true", True)
|
||||
assert (ev, cur, adv) == (None, None, True)
|
||||
|
||||
|
||||
def test_normaliza_flag_advance_presencia_sin_valor():
|
||||
# `--advance` solo (sin valor): presencia => True.
|
||||
ev, cur, adv = _normalize_fn_run_flags("--advance", None, True)
|
||||
assert (ev, cur, adv) == (None, None, True)
|
||||
|
||||
|
||||
def test_normaliza_flag_advance_cero_es_false():
|
||||
ev, cur, adv = _normalize_fn_run_flags("--advance", "0", True)
|
||||
assert adv is False
|
||||
|
||||
|
||||
def test_no_toca_llamada_normal_por_kwargs():
|
||||
# Una llamada normal con rutas reales no se altera.
|
||||
ev, cur, adv = _normalize_fn_run_flags("/tmp/q.jsonl", "/tmp/cursor", False)
|
||||
assert (ev, cur, adv) == ("/tmp/q.jsonl", "/tmp/cursor", False)
|
||||
ev2, cur2, adv2 = _normalize_fn_run_flags(None, None, True)
|
||||
assert (ev2, cur2, adv2) == (None, None, True)
|
||||
|
||||
|
||||
def test_peek_flag_posicional_no_mueve_cursor(tmp_path):
|
||||
# End-to-end del patron `--advance false` sobre una cola controlada: como el
|
||||
# normalizador descarta las rutas, apuntamos via env HOME a tmp_path.
|
||||
fleet_dir = tmp_path / ".claude" / "fleet"
|
||||
fleet_dir.mkdir(parents=True)
|
||||
_write_queue(fleet_dir / "events.jsonl", [_make_event("RECLAMA")])
|
||||
import os as _os
|
||||
|
||||
prev_home = _os.environ.get("HOME")
|
||||
_os.environ["HOME"] = str(tmp_path)
|
||||
try:
|
||||
# Simula exactamente lo que pasa el runner de `fn run`.
|
||||
result = drain_fleet_events("--advance", "false")
|
||||
finally:
|
||||
if prev_home is not None:
|
||||
_os.environ["HOME"] = prev_home
|
||||
assert result["total_new"] == 1
|
||||
# advance=False => no se persiste cursor.
|
||||
assert not (fleet_dir / "cursor").exists()
|
||||
|
||||
|
||||
def test_cursor_mayor_que_lineas_reinicia(tmp_path):
|
||||
events_path = tmp_path / "events.jsonl"
|
||||
cursor_path = tmp_path / "cursor"
|
||||
|
||||
Reference in New Issue
Block a user