3ae472d1f3
- Nuevo estado de reposo 'preguntando' (❓ esperando respuesta), distinto de pendiente_revision: lo usa el Stop worker cuando la respuesta termina con preguntas concretas al usuario en vez de dejar un resultado para revisar. - DoD corto opcional junto al objetivo: se fija con "dod: <texto>" ("dod: clear" lo borra) y se muestra atenuado con 🏁 tras el objetivo. Re-fijar el objetivo preserva el DoD existente. - Comando "pausa" (prompt) marca la fase en en_pausa. Es la alternativa manual a Ctrl-C: Claude Code no dispara ningun hook al interrumpir un turno (el Stop hook solo corre en finalizacion normal; feature pedido, sin implementar), asi que no es posible detectar la interrupcion automaticamente. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
118 lines
4.9 KiB
Bash
Executable File
118 lines
4.9 KiB
Bash
Executable File
#!/bin/bash
|
|
# Worker del Stop hook: resuelve el estado de REPOSO de la tarea cuando el
|
|
# asistente para y cede el control. Clasifica con ask_llm (haiku, API directa;
|
|
# nunca `claude -p`, ver regla llm_invocation.md) y lo escribe en el goal JSON
|
|
# manteniendo el historial.
|
|
#
|
|
# El estado ACTIVO (mientras se trabaja: investigando/haciendo/testeando) lo
|
|
# marca el hook PostToolUse (goal_phase_active.sh), de forma determinista. Este
|
|
# worker SOLO produce estados de reposo: hecho, pendiente_revision, bloqueado,
|
|
# en_pausa.
|
|
#
|
|
# Args: <session_id> <transcript_path> <goal_json>
|
|
|
|
SID="$1"
|
|
TRANSCRIPT="$2"
|
|
F="$3"
|
|
|
|
PY="$HOME/fn_registry/python/.venv/bin/python3"
|
|
ASK="$HOME/fn_registry/python/functions/core/ask_llm.py"
|
|
|
|
[ -x "$PY" ] || exit 0
|
|
[ -f "$ASK" ] || exit 0
|
|
[ -f "$F" ] || exit 0
|
|
[ -f "$TRANSCRIPT" ] || exit 0
|
|
|
|
GOAL=$(jq -r '.goal // ""' "$F" 2>/dev/null)
|
|
[ -z "$GOAL" ] && exit 0
|
|
CUR=$(jq -r '.phase // ""' "$F" 2>/dev/null)
|
|
|
|
is_active() {
|
|
case "$1" in
|
|
investigando|planificando|haciendo|testeando|puliendo) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# Una pasada de abajo a arriba sobre el turno actual: ultima respuesta de texto
|
|
# del asistente + ultima peticion del usuario + si hubo trabajo (tool_use).
|
|
LAST=""
|
|
USER_MSG=""
|
|
HAS_WORK=0
|
|
while IFS= read -r line; do
|
|
t=$(printf '%s' "$line" | jq -r '.type // empty' 2>/dev/null)
|
|
if [ "$t" = "assistant" ]; then
|
|
if [ -z "$LAST" ]; then
|
|
txt=$(printf '%s' "$line" | jq -r '(.message.content // [])[]? | select(.type=="text") | .text' 2>/dev/null)
|
|
[ -n "$txt" ] && LAST="$txt"
|
|
fi
|
|
if printf '%s' "$line" | jq -e '(.message.content // [])[]? | select(.type=="tool_use")' >/dev/null 2>&1; then
|
|
HAS_WORK=1
|
|
fi
|
|
elif [ "$t" = "user" ]; then
|
|
ctype=$(printf '%s' "$line" | jq -r '.message.content | type' 2>/dev/null)
|
|
if [ "$ctype" = "string" ]; then
|
|
USER_MSG=$(printf '%s' "$line" | jq -r '.message.content' 2>/dev/null)
|
|
break
|
|
fi
|
|
if ! printf '%s' "$line" | jq -e '(.message.content // [])[]? | select(.type=="tool_result")' >/dev/null 2>&1; then
|
|
USER_MSG=$(printf '%s' "$line" | jq -r '(.message.content // [])[]? | select(.type=="text") | .text' 2>/dev/null)
|
|
break
|
|
fi
|
|
fi
|
|
done < <(tac "$TRANSCRIPT")
|
|
|
|
# Solo resolver reposo si hubo trabajo este turno o si veniamos de un estado
|
|
# activo (paramos tras currar). Charla sobre un reposo previo: no tocar.
|
|
if [ "$HAS_WORK" = "0" ] && ! is_active "$CUR"; then
|
|
exit 0
|
|
fi
|
|
|
|
LAST=$(printf '%s' "$LAST" | tail -c 4000)
|
|
[ -z "$LAST" ] && exit 0
|
|
USER_MSG=$(printf '%s' "$USER_MSG" | tail -c 1500)
|
|
|
|
SYS="El asistente acaba de PARAR y cede el control al usuario. Clasifica el estado de REPOSO en que queda la tarea. Responde UNA sola palabra, sin nada mas, de: hecho pendiente_revision preguntando bloqueado en_pausa sin_cambio. hecho=el objetivo esta completo y verificado; pendiente_revision=el asistente termino un trabajo y espera que el humano lo revise o apruebe (no hace una pregunta directa); preguntando=el asistente termina formulando una o varias PREGUNTAS concretas al usuario y necesita su respuesta o decision para continuar; bloqueado=no puede avanzar por un error o por falta de informacion/acceso; en_pausa=hizo un avance y espera la siguiente indicacion, sin estar terminado ni preguntar ni bloqueado; sin_cambio=el turno no altera el estado de reposo actual (charla irrelevante). Distingue: si la respuesta acaba con preguntas al usuario es 'preguntando'; si deja un resultado para que lo mire es 'pendiente_revision'. Usa 'hecho' SOLO si el trabajo esta completo y confirmado."
|
|
|
|
PROMPT="OBJETIVO DE LA TAREA: ${GOAL}
|
|
|
|
ULTIMA PETICION DEL USUARIO:
|
|
${USER_MSG}
|
|
|
|
ULTIMA RESPUESTA DEL ASISTENTE:
|
|
${LAST}
|
|
|
|
Responde con una sola palabra de la lista permitida:"
|
|
|
|
RAW=$("$PY" "$ASK" --model claude-haiku-4-5-20251001 --system "$SYS" "$PROMPT" 2>/dev/null | tr '[:upper:]' '[:lower:]')
|
|
[ -z "$RAW" ] && exit 0
|
|
|
|
case "$RAW" in
|
|
*sin_cambio*|*sincambio*|*ninguna*|*charla*) exit 0 ;;
|
|
*pregunt*|*consulta*|*respuesta*) PHASE=preguntando ;;
|
|
*pendiente*revis*|*revis*|*aprob*) PHASE=pendiente_revision ;;
|
|
*bloque*) PHASE=bloqueado ;;
|
|
*hecho*|*complet*|*termin*|*done*) PHASE=hecho ;;
|
|
*pausa*|*pause*|*siguiente*) PHASE=en_pausa ;;
|
|
# Si por error devuelve un estado activo al parar, lo tratamos como pausa.
|
|
investigando|planificando|haciendo|testeando|puliendo) PHASE=en_pausa ;;
|
|
*) exit 0 ;;
|
|
esac
|
|
|
|
# Escribir la fase + mantener historial (append solo si cambia respecto al
|
|
# ultimo; se conservan los ultimos 12 estados).
|
|
TMP="${F}.tmp.$$"
|
|
if jq --arg p "$PHASE" '
|
|
.phase = $p
|
|
| .history = (
|
|
( .history // [] ) as $h
|
|
| ( if ($h | length) > 0 and ($h[-1] == $p) then $h else ($h + [$p]) end )
|
|
| .[-12:]
|
|
)
|
|
' "$F" > "$TMP" 2>/dev/null; then
|
|
mv "$TMP" "$F"
|
|
else
|
|
rm -f "$TMP"
|
|
fi
|
|
exit 0
|