feat: scaffold claude_session — daemon de sesion claude caliente (NDJSON)

Daemon de larga vida que mantiene una TUI claude interactiva viva y responde
prompts en ~2.7s, embebible como subproceso via NDJSON por stdin/stdout.

Arranca mitmproxy (addon tee_anthropic_sse) + claude TUI en PTY (creack/pty
directo, persistente) una vez. Cada prompt se teclea en la TUI viva; la
respuesta se lee del SSE de la red (exacta, corta en message_stop). El cold
start (~7s) se paga una vez; los siguientes mensajes ~2.7s, con memoria entre
turnos. Protocolo: send/restart/shutdown -> ready/text_delta/result/restarted.

Validado: 2.7s por mensaje en caliente (vs 15s parseando TUI, vs 9s one-shot),
restart relanza la conversacion. Reusa tee_anthropic_sse_py_cybersecurity +
vt_render_go_tui.
This commit is contained in:
agent
2026-06-04 00:40:29 +02:00
commit d9982d853d
6 changed files with 632 additions and 0 deletions
+154
View File
@@ -0,0 +1,154 @@
---
name: claude_session
lang: go
domain: infra
version: 0.1.0
description: "Daemon de larga vida que mantiene una sesion claude (TUI interactiva) caliente y responde prompts rapido, pensado para embeber en una app como subproceso via NDJSON por stdin/stdout. Arranca mitmproxy + claude TUI una vez; cada prompt se teclea en la TUI viva y la respuesta se lee del SSE de la red (exacta, corta en message_stop). Primer mensaje paga el arranque (~7s); los siguientes ~2-3s, con memoria entre turnos. Comando restart para reiniciar la conversacion."
tags: [cli, claude, daemon, session, ndjson, mitmproxy, web-proxy, streaming]
uses_functions:
- tee_anthropic_sse_py_cybersecurity
- vt_render_go_tui
uses_types: []
framework: ""
entry_point: "main.go"
dir_path: "apps/claude_session"
icon:
phosphor: "lightning"
accent: "#f59e0b"
e2e_checks:
- id: build
cmd: "go build -o claude_session ."
timeout_s: 60
- id: bad_cmd
cmd: "echo '{\"cmd\":\"bogus\"}' | timeout 30 ./claude_session 2>/dev/null | grep -q 'unknown cmd' && echo ok || true"
expect_stdout_contains: "ok"
timeout_s: 40
---
# claude_session
## Que hace
Daemon que mantiene una sesion `claude` (TUI interactiva real, **nunca `claude -p`**) **caliente**
y responde prompts en ~2-3s. Pensado para embeberse en una app como subproceso de larga vida,
controlado por NDJSON por stdin/stdout.
Arranca **una vez** un mitmproxy (addon `tee_anthropic_sse`) y la TUI de claude en un PTY, y los
mantiene vivos. Cada prompt se teclea en la TUI viva; la respuesta se lee **del SSE de la red**
(exacta, token a token, cortada por `message_stop`). El cold start (~7s) se paga una sola vez al
arrancar; los mensajes siguientes solo pagan la generacion, y la conversacion **mantiene contexto
entre turnos**.
## Latencia (medida)
| Camino | por mensaje |
|---|---|
| `claude_pipe` (parsear render TUI) | ~15s |
| `claude_wire` (interceptar red, one-shot) | ~9s |
| **`claude_session` (daemon caliente)** | **~2.7s** (+ ~7s de arranque, una vez) |
## Protocolo NDJSON
Un objeto JSON por linea.
**stdin (comandos):**
```json
{"cmd":"send","prompt":"hola"}
{"cmd":"restart"}
{"cmd":"shutdown"}
```
**stdout (eventos):**
```json
{"type":"ready"}
{"type":"text_delta","text":"H"}
{"type":"text_delta","text":"ola"}
{"type":"result","result":"Hola"}
{"type":"restarted"}
{"type":"error","message":"..."}
```
- `send`: teclea el prompt en la TUI viva, emite `text_delta` segun llegan del SSE, luego `result`
y un `ready` cuando la TUI vuelve al input box.
- `restart`: mata y relanza la TUI (conversacion **nueva, sin memoria**), mantiene el mitmproxy.
Emite `restarted` + `ready`.
- `shutdown`: mata todo y termina.
## Arquitectura
```
mitmdump + tee_anthropic_sse (persistente — capta el SSE de /v1/messages)
claude TUI en PTY (creack/pty) (persistente — NO se mata entre prompts)
loop NDJSON (teclea prompts, lee el wire, multiplexa por message_stop)
```
Reusa `tee_anthropic_sse_py_cybersecurity` (addon SSE) y `vt_render_go_tui` (render del PTY para
detectar readiness del input box).
## Embeber en una app
La app arranca el binario como subproceso de larga vida y habla por sus stdin/stdout:
```python
import subprocess, json
p = subprocess.Popen(["claude_session", "--cwd", "/home/enmanuel/fn_registry"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, bufsize=1)
# esperar {"type":"ready"}
def send(prompt):
p.stdin.write(json.dumps({"cmd": "send", "prompt": prompt}) + "\n"); p.stdin.flush()
for line in p.stdout:
ev = json.loads(line)
if ev["type"] == "result":
return ev["result"]
```
## Ejemplo (NDJSON por stdin)
```bash
cd apps/claude_session && go build -o claude_session .
printf '%s\n' \
'{"cmd":"send","prompt":"di solo OK"}' \
'{"cmd":"send","prompt":"di solo DOS"}' \
'{"cmd":"shutdown"}' \
| ./claude_session --cwd /home/enmanuel/fn_registry
# {"type":"ready"}
# {"type":"text_delta","text":"O"} ... {"type":"result","result":"OK"} {"type":"ready"}
# {"type":"text_delta","text":"D"} ... {"type":"result","result":"DOS"} {"type":"ready"}
```
## Flags
| Flag | Default | Que hace |
|---|---|---|
| `--cwd` | `~/fn_registry` | Directorio de la sesion claude (MCP aprobados). |
| `--port` | `8901` | Puerto del mitmproxy. |
| `--root` | `~/fn_registry` | Raiz del registry (para localizar el addon). |
| `--addon` | `<root>/python/functions/cybersecurity/tee_anthropic_sse.py` | Addon mitmproxy. |
| `--ca` | `~/.mitmproxy/mitmproxy-ca-cert.pem` | CA de mitmproxy. |
| `--bin` | `claude` | Binario claude. |
| `--warmup` | `12s` | Espera maxima a que la TUI este lista. |
## Prerrequisitos
- `mitmproxy` (`mitmdump` en PATH) + CA generada y confiada via `NODE_EXTRA_CA_CERTS`.
- `claude` en el PATH.
## Gotchas
- **Readiness post-restart prematura**: tras `restart`, el `ready` puede emitirse antes de que la
TUI termine de cargar (detecta el input box `` pronto). El primer `send` tras un restart puede
tardar mas (~5-6s en vez de ~2.7s) porque el input se teclea mientras claude aun arranca (se
bufferea, no se pierde). Refinamiento pendiente: readiness mas estricta (esperar que cese el
trafico de arranque en el proxy).
- **Sesion secuencial**: un prompt a la vez. No mandes un `send` mientras otro genera.
- **Memoria entre turnos**: la TUI viva acumula la conversacion (es una feature). Usa `restart`
para empezar de cero.
- **Requiere mitmproxy + CA** (`NODE_EXTRA_CA_CERTS`); depende de que claude no haga TLS pinning
(hoy no lo hace).
- **Una interaccion dispara varias /v1/messages**; el addon filtra por `has_tools` para seguir la
respuesta principal.
- **Linux/Unix** (PTY POSIX).
- **Es trafico de tu propia cuenta y maquina** — observabilidad local.