Captain Mobile v3.1 - App funcional

- Backend: claude -p --output-format stream-json
- --resume para mantener contexto de conversación
- Auto-connect al crear sesión
- UI simplificada (botón + y logout)
- Servicio systemd: captain-claude

Siguiente paso: sesiones screen

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
ARCHITECT
2026-01-18 00:03:15 +00:00
parent f199daf4ba
commit 48e66b1129

View File

@@ -1,164 +1,131 @@
# Captain Claude Mobile v2 - Estado del Proyecto # Captain Claude Mobile - Estado del Proyecto
**Fecha:** 2026-01-17 **Fecha:** 2026-01-17
**Estado:** BACKEND ACTUALIZADO / PENDIENTE COMPILAR APK **Versión:** v3.1
**Estado:** FUNCIONAL
--- ---
## Cambios Realizados (17 Ene 2026) ## Resumen
### Backend reescrito con patrón LLMChat App móvil para chatear con Claude Code usando `claude -p --output-format stream-json`.
Se aplicó el patrón de streaming de [LLMChat](https://github.com/c0sogi/LLMChat):
1. **Queue para desacoplar receiver/sender**
- `asyncio.Queue` para mensajes entrantes
- `ws_receiver()` y `ws_sender()` corren en paralelo con `asyncio.gather()`
2. **ChatBuffer** - Contexto por conexión
- Mantiene estado: `websocket`, `username`, `queue`, `done`, `conversation_id`
- `done` event para interrumpir streaming
3. **Mensaje `init` al conectar**
- Envía lista de conversaciones al conectar
- Frontend recibe estado inicial sin llamada REST adicional
4. **Soporte para interrumpir streaming**
- Cliente envía `{"type": "stop"}`
- Backend termina proceso Claude y envía `{"type": "interrupted"}`
5. **Nuevos tipos de mensaje**
- `init` - Estado inicial con conversaciones
- `text_start` - Inicio de bloque de texto
- `tool_input` - Input completo del tool
- `interrupted` - Generación interrumpida
- `conversation_loaded` - Conversación cargada via WS
--- ---
## Configuración Actual ## Arquitectura v3
| Componente | Puerto | Notas | ### Backend (`captain_api_v3.py`)
|------------|--------|-------|
| **captain-api-v2** | **3030** | Backend con patrón LLMChat |
**Credenciales:** `admin` / `admin` - **Puerto:** 3030
**Servicio:** `captain-api-v2.service` (systemd) - **Servicio systemd:** `captain-claude`
- **Método:** `claude -p --output-format stream-json`
- **Contexto:** `--resume SESSION_ID` mantiene historial de conversación
- **Auto-connect:** Al crear sesión, se conecta automáticamente
- **Almacenamiento:** Sesiones en memoria (se pierden al reiniciar)
### Flutter App
- **APK:** `260117_captain-claude-v3.1.apk`
- **UI:** Simplificada - botón + para nuevo chat, logout
- **Conexión:** WebSocket a `wss://captain.tzzrarchitect.me/ws/chat`
--- ---
## Archivos Modificados ## API WebSocket
### Mensajes del Cliente
```json
{"token": "jwt"} // Autenticación
{"type": "create_session", "name": "Chat 1"} // Crear sesión
{"type": "message", "content": "hola"} // Enviar mensaje
{"type": "ping"} // Keep-alive
```
### Respuestas del Servidor
```json
{"type": "init", "sessions": [...]} // Estado inicial
{"type": "session_created", "session_id": "x", "name": "y"} // Sesión creada
{"type": "session_connected", "session_id": "x", "name": "y"} // Conectado
{"type": "output", "content": "..."} // Respuesta de Claude
{"type": "done", "session_id": "uuid"} // Respuesta completa
{"type": "error", "message": "..."} // Error
```
---
## Archivos
``` ```
apps/captain-mobile-v2/ apps/captain-mobile-v2/
├── backend/ ├── backend/
│ ├── captain_api_v2.py # Backend reescrito │ ├── captain_api_v3.py # Backend actual
── captain-api-v2.service # Puerto 3031 ── captain-claude.service # Servicio systemd
├── flutter/lib/ │ └── test_*.py # Tests
│ ├── config/api_config.dart # Puerto 3031 ├── flutter/
│ ├── services/chat_service.dart # Nuevos eventos │ ├── lib/
└── providers/chat_provider.dart # Manejo de init, interrupted, etc. │ ├── screens/chat_screen.dart
└── ESTADO_PROYECTO.md # Este archivo ├── providers/chat_provider.dart
│ │ ├── services/chat_service.dart
│ │ └── config/api_config.dart
│ └── build/app/outputs/flutter-apk/app-release.apk
└── ESTADO_PROYECTO.md
``` ```
--- ---
## Próximos Pasos ## Comandos Útiles
### 1. Instalar servicio systemd (requiere sudo)
```bash ```bash
sudo cp captain-api-v2.service /etc/systemd/system/ # Estado del servicio
sudo systemctl daemon-reload systemctl status captain-claude
sudo systemctl enable captain-api-v2
sudo systemctl start captain-api-v2
```
### 2. Compilar APK # Logs
```bash journalctl -u captain-claude -f
# Reiniciar
systemctl restart captain-claude
# Compilar APK
cd /home/architect/captain-claude/apps/captain-mobile-v2/flutter cd /home/architect/captain-claude/apps/captain-mobile-v2/flutter
/home/architect/flutter/bin/flutter build apk --release /home/architect/flutter/bin/flutter build apk --release
```
### 3. Subir a Nextcloud # Subir a Nextcloud
```bash /home/architect/bin/rclone copy build/app/outputs/flutter-apk/app-release.apk nextcloud-architect:app/
scp -i ~/.ssh/tzzr build/app/outputs/flutter-apk/app-release.apk \
root@72.62.1.113:"/var/www/nextcloud/data/tzzrdeck/files/documentos adjuntos/captain-mobile-v2.apk"
ssh -i ~/.ssh/tzzr root@72.62.1.113 \
"chown www-data:www-data '/var/www/nextcloud/data/tzzrdeck/files/documentos adjuntos/captain-mobile-v2.apk' && \
cd /var/www/nextcloud && sudo -u www-data php occ files:scan tzzrdeck"
``` ```
--- ---
## Probar Backend Manualmente ## Siguiente Paso: Sesiones Screen
```bash ### Objetivo
# Health check
curl http://localhost:3031/health
# Login Crear versión capaz de:
TOKEN=$(curl -s -X POST http://localhost:3031/auth/login \ 1. Listar sesiones screen existentes
-H "Content-Type: application/json" \ 2. Conectarse a sesiones screen en tiempo real
-d '{"username":"admin","password":"admin"}' | jq -r '.token') 3. Crear nuevas sesiones screen con Claude Code
4. Enviar input a sesiones activas
# Listar conversaciones ### Diferencia
curl -H "Authorization: Bearer $TOKEN" http://localhost:3031/conversations
``` | v3 Actual | Screen Sessions |
|-----------|-----------------|
| `claude -p` por mensaje | Proceso persistente en screen |
| Sesiones en memoria | Sesiones reales en servidor |
| Solo texto limpio | Output completo |
| Sin acceso a otras screens | Acceso a cualquier screen |
### Implementación
1. Backend v4 con endpoints para screen
2. Parsing de output ANSI
3. Flutter con selector de screens
--- ---
## Arquitectura WebSocket (LLMChat pattern) ## Historial
``` - **v3.1** (17/01/26): UI simplificada, auto-connect en backend
┌─────────────────┐ WebSocket ┌─────────────────┐ - **v3.0** (17/01/26): Cambio a `claude -p --output-format stream-json`
│ Flutter Client │ ◄───────────────► │ FastAPI Server │ - **v2.x** (17/01/26): Intentos con screen+hardcopy (descartado)
│ │ /ws/chat │ │
└─────────────────┘ └────────┬────────┘
┌──────────────────────────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
ws_receiver() ws_sender() ChatBuffer
- Recibe JSON - Procesa queue - websocket
- stop → buffer.done.set() - Llama Claude - username
- ping → pong - Stream chunks - queue
- Otros → queue.put() - Guarda en DB - done event
```
---
## Flujo de Mensajes
### Conexión
```
Cliente Servidor
│ │
│──── {"token": "xxx"} ─────────►│
│ │
│◄─── {"type": "init", │
│ "user": "admin", │
│ "conversations": [...]} │
```
### Chat
```
Cliente Servidor
│ │
│──── {"type": "message", │
│ "content": "Hola"} ──────►│
│ │
│◄─── {"type": "start"} ─────────│
│◄─── {"type": "thinking"} ──────│
│◄─── {"type": "delta", ...} ────│ (múltiples)
│◄─── {"type": "done", ...} ─────│
```
### Interrumpir
```
Cliente Servidor
│ │
│──── {"type": "stop"} ─────────►│
│ │
│◄─── {"type": "interrupted", │
│ "content": "..."} ────────│
```