JARVIS: asistente de voz local para Linux
Nucleo propio: oye con whisper.cpp, piensa con un modelo de Ollama, habla con Piper, y hace RAG sobre los apuntes del usuario. 100% local, sin cuentas ni claves. Escrito bajo una restriccion dura, 4 GB de VRAM: el cerebro y whisper comparten tarjeta y solo caben porque estan dimensionados para ello. El RAG usa embeddings estaticos con busqueda hibrida; la voz clonada se sirve de una cache de frases. Incluye instalador (install.sh), requisitos, y documentacion del stack, del manejo de root y de las acciones. Los apuntes indexados y el diario NO se incluyen: son privados y el .gitignore los bloquea.
This commit is contained in:
commit
8e4bc8ad94
125 changed files with 25033 additions and 0 deletions
207
config/noche.sh
Executable file
207
config/noche.sh
Executable file
|
|
@ -0,0 +1,207 @@
|
|||
#!/usr/bin/env bash
|
||||
# Pipeline nocturno: JARVIS se mejora solo mientras nadie mira.
|
||||
#
|
||||
# Cada ciclo hace lo mismo: mira los datos que ha dejado el uso real, propone
|
||||
# un cambio, lo aplica, y lo somete a config/puerta.sh. Si la puerta dice que
|
||||
# no, se revierte ENTERO con git y se pasa al siguiente. Nada se queda sin
|
||||
# pasar por ahi.
|
||||
#
|
||||
# Por que git y no una copia: el arbol ya esta versionado, `git checkout` es
|
||||
# atomico y deja rastro de lo que se intento. Al terminar la noche se puede ver
|
||||
# commit a commit que se cambio y por que.
|
||||
#
|
||||
# noche.sh corre hasta que se le diga que pare
|
||||
# noche.sh --ciclos 5 solo cinco vueltas
|
||||
# noche.sh --seco propone y valida, pero NO commitea (para mirar)
|
||||
#
|
||||
# Para pararlo: touch ~/COFRE/CODERS/JARVIS/PARAR
|
||||
#
|
||||
# ── Que puede mejorar solo, y que no ────────────────────────────────────────
|
||||
#
|
||||
# Se mejora solo lo que se puede VALIDAR solo. Eso es:
|
||||
# · el vocabulario: alias nuevos para ordenes que no se entendieron
|
||||
# · las frases del catalogo: variantes de las que fallan
|
||||
# · la voz: regenerar en la voz buena las frases que cayeron a la de reserva
|
||||
# · el prompt: mantenerlo al dia con el catalogo
|
||||
# · acciones nuevas a partir de lo que se pidio y no existia
|
||||
#
|
||||
# NO se toca solo el motor, el panel ni la logica de la aplicacion. No porque
|
||||
# sea imposible generarlo, sino porque no hay forma automatica de comprobar que
|
||||
# un cambio ahi es bueno: la suite dice si algo se rompio, no si algo mejoro.
|
||||
# Un bucle que reescribe su propio validador acaba pasando siempre.
|
||||
set -uo pipefail
|
||||
|
||||
# ── DESACTIVADO EL 15 DE AGOSTO DE 2026 ────────────────────────────────────
|
||||
#
|
||||
# Este bucle modifica el arbol de `newelle/` y COMMITEA solo. Ese arbol esta
|
||||
# congelado en la etiqueta jarvis-newelle-final desde que el desarrollo se
|
||||
# mudo a nucleo/: dejarlo correr escribiria commits automaticos sobre un
|
||||
# repositorio que por definicion ya no cambia.
|
||||
#
|
||||
# Se ha quitado tambien su temporizador de systemd —estaba armado para las
|
||||
# 03:30 y habria saltado solo—, movido a ~/.config/systemd/user/desactivados/.
|
||||
#
|
||||
# Esto de aqui es la segunda cerradura: aunque alguien lo lance a mano, se
|
||||
# niega. Para retomarlo hay que apuntarlo primero al nucleo, y eso no es
|
||||
# cambiar una ruta: la puerta (config/puerta.sh) valida el catalogo, el
|
||||
# generador y la suite VIEJOS, y el nucleo tiene los suyos.
|
||||
if [ "${JARVIS_NOCHE_A_SABIENDAS:-}" != "si" ]; then
|
||||
cat >&2 <<'AVISO'
|
||||
noche.sh esta desactivado.
|
||||
|
||||
Modifica y commitea sobre newelle/, que esta congelado en jarvis-newelle-final
|
||||
desde que el desarrollo se mudo a nucleo/. Su temporizador de systemd tambien
|
||||
se retiro.
|
||||
|
||||
Para retomarlo hay que reescribirlo contra el nucleo: la puerta valida el
|
||||
catalogo, el generador y la suite viejos.
|
||||
|
||||
Si de verdad quieres correrlo tal cual sobre el arbol congelado:
|
||||
JARVIS_NOCHE_A_SABIENDAS=si config/noche.sh
|
||||
AVISO
|
||||
exit 78
|
||||
fi
|
||||
|
||||
RAIZ=$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)
|
||||
cd "$RAIZ"
|
||||
PARADA=$RAIZ/PARAR
|
||||
DIARIO=$RAIZ/noche.log
|
||||
CICLOS=0; SECO=no
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--ciclos) CICLOS=${2:-0}; shift 2;;
|
||||
--seco) SECO=si; shift;;
|
||||
*) shift;;
|
||||
esac
|
||||
done
|
||||
|
||||
log() { printf '%s %s\n' "$(date +%H:%M:%S)" "$*" | tee -a "$DIARIO"; }
|
||||
|
||||
# ── salvaguarda ────────────────────────────────────────────────────────────
|
||||
cd "$RAIZ/newelle"
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
log "ABORTO: el fork tiene cambios sin commitear. Commitealos o descartalos."
|
||||
exit 1
|
||||
fi
|
||||
ANCLA=$(git rev-parse HEAD)
|
||||
git tag -f "noche-$(date +%Y%m%d-%H%M)" >/dev/null 2>&1
|
||||
log "ancla: $ANCLA (para volver: git reset --hard $ANCLA)"
|
||||
cd "$RAIZ"
|
||||
|
||||
# ── las mejoras, cada una en su funcion ────────────────────────────────────
|
||||
# Cada una devuelve 0 si propuso algo, 1 si no habia nada que hacer.
|
||||
|
||||
mejora_vocabulario() {
|
||||
# Lo que el usuario dijo y no se entendio. Es la fuente mas valiosa porque
|
||||
# son fallos REALES, no inventados.
|
||||
local registro=$HOME/.var/app/io.github.qwersyk.Newelle/config/jarvis-sin-accion.jsonl
|
||||
[ -f "$registro" ] || return 1
|
||||
python3 "$RAIZ/config/proponer_alias.py" --aplicar 2>&1 | tail -3
|
||||
return ${PIPESTATUS[0]}
|
||||
}
|
||||
|
||||
mejora_frases() {
|
||||
# Variantes de las frases del catalogo, generadas con el modelo local y
|
||||
# filtradas duro. Sube la cobertura sin tocar comandos.
|
||||
python3 "$RAIZ/config/proponer_frases.py" --aplicar 2>&1 | tail -3
|
||||
return ${PIPESTATUS[0]}
|
||||
}
|
||||
|
||||
mejora_voz() {
|
||||
# Las frases que cayeron a la voz de reserva se regeneran con la clonada.
|
||||
[ -s "$RAIZ/voz/misses.log" ] || return 1
|
||||
"$RAIZ/voz/clonador-venv/bin/python" "$RAIZ/voz/calienta_cache.py" cpu 2>&1 | tail -2
|
||||
return 0
|
||||
}
|
||||
|
||||
mejora_prompt() {
|
||||
# El prompt lleva la lista de capacidades: si cambio el catalogo, hay que
|
||||
# regenerarlo o el modelo sigue creyendo que sabe hacer otra cosa.
|
||||
flatpak run --command=python3 io.github.qwersyk.Newelle//master \
|
||||
"$RAIZ/config/set_persona.py" 2>&1 | grep -v WARNING | tail -1
|
||||
return 0
|
||||
}
|
||||
|
||||
mejora_reconocimiento() {
|
||||
# La unica que genera sus PROPIOS datos: dice las frases del catalogo con
|
||||
# varias voces, velocidades y ruidos, las transcribe con el mismo Whisper
|
||||
# del asistente, y propone alias para lo que no llega a su accion.
|
||||
#
|
||||
# Es la que hace productiva la noche aunque nadie hable con JARVIS. Ya trae
|
||||
# su propio guardarrail —solo aplica si el acierto sube Y la charla sigue
|
||||
# yendo al modelo— y ademas pasa por la puerta como todo lo demas.
|
||||
timeout 3000 python3 "$RAIZ/config/mejora_nocturna.py" --ronda --muestras 2 2>&1 | tail -4
|
||||
return ${PIPESTATUS[0]}
|
||||
}
|
||||
|
||||
MEJORAS=(mejora_reconocimiento mejora_vocabulario mejora_frases mejora_prompt mejora_voz)
|
||||
|
||||
# ── el bucle ───────────────────────────────────────────────────────────────
|
||||
vuelta=0
|
||||
aceptados=0; rechazados=0; sin_nada=0
|
||||
log "=== empieza la noche ==="
|
||||
|
||||
while true; do
|
||||
[ -f "$PARADA" ] && { log "parada pedida (existe $PARADA)"; break; }
|
||||
[ "$CICLOS" -gt 0 ] && [ "$vuelta" -ge "$CICLOS" ] && { log "hechas $CICLOS vueltas"; break; }
|
||||
vuelta=$((vuelta+1))
|
||||
|
||||
for mejora in "${MEJORAS[@]}"; do
|
||||
[ -f "$PARADA" ] && break
|
||||
log "── vuelta $vuelta · $mejora"
|
||||
|
||||
salida=$($mejora 2>&1); resultado=$?
|
||||
[ -n "$salida" ] && echo "$salida" | sed 's/^/ /' | tee -a "$DIARIO" >/dev/null
|
||||
|
||||
cd "$RAIZ/newelle"
|
||||
cambios=$(git status --porcelain)
|
||||
cd "$RAIZ"
|
||||
if [ -z "$cambios" ] && [ "$resultado" -ne 0 ]; then
|
||||
log " nada que proponer"
|
||||
sin_nada=$((sin_nada+1))
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -z "$cambios" ]; then
|
||||
log " aplicado fuera del fork (voz o ajustes), sin commit"
|
||||
aceptados=$((aceptados+1))
|
||||
continue
|
||||
fi
|
||||
|
||||
# ── LA PUERTA ──
|
||||
if timeout 2400 "$RAIZ/config/puerta.sh" completa > "$RAIZ/.puerta.txt" 2>&1; then
|
||||
if [ "$SECO" = si ]; then
|
||||
log " [seco] habria pasado la puerta; se revierte igual"
|
||||
(cd "$RAIZ/newelle" && git checkout -- . && git clean -fd >/dev/null 2>&1)
|
||||
else
|
||||
(cd "$RAIZ/newelle" && git add -A && \
|
||||
git commit -q -m "auto($mejora): mejora nocturna
|
||||
|
||||
Propuesto y validado por config/noche.sh sin intervencion. Paso la
|
||||
puerta completa: catalogo valido, generador reproducible, emparejador
|
||||
sin perdidas, 150 acciones respondiendo y la suite en verde.
|
||||
|
||||
$(echo "$salida" | head -3)")
|
||||
log " ACEPTADO y commiteado"
|
||||
aceptados=$((aceptados+1))
|
||||
fi
|
||||
else
|
||||
log " RECHAZADO: $(tail -1 "$RAIZ/.puerta.txt")"
|
||||
(cd "$RAIZ/newelle" && git checkout -- . && git clean -fd >/dev/null 2>&1)
|
||||
rechazados=$((rechazados+1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Si una vuelta entera no propuso nada, no tiene sentido seguir dando
|
||||
# vueltas en vacio: se espera a que el uso genere datos nuevos.
|
||||
if [ "$sin_nada" -ge $(( ${#MEJORAS[@]} )) ]; then
|
||||
log "una vuelta entera sin nada que hacer; esperando 30 min"
|
||||
for _ in $(seq 180); do [ -f "$PARADA" ] && break; sleep 10; done
|
||||
sin_nada=0
|
||||
fi
|
||||
done
|
||||
|
||||
log "=== fin: $aceptados aceptados, $rechazados rechazados, $vuelta vueltas ==="
|
||||
cd "$RAIZ/newelle"
|
||||
log "commits de esta noche:"
|
||||
git log --oneline "$ANCLA"..HEAD 2>/dev/null | sed 's/^/ /' | tee -a "$DIARIO"
|
||||
Loading…
Add table
Add a link
Reference in a new issue