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:
@@ -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": "..."} ────────│
|
|
||||||
```
|
|
||||||
|
|||||||
Reference in New Issue
Block a user