fad4006f60
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
135 lines
5.7 KiB
Markdown
135 lines
5.7 KiB
Markdown
---
|
|
id: "0071a"
|
|
title: "Extraer `claude_chat_panel` a cpp/functions/core/ (sub-issue de 0071)"
|
|
status: pendiente
|
|
type: feature
|
|
domain:
|
|
- registry-quality
|
|
scope: registry-only
|
|
priority: alta
|
|
depends:
|
|
- "0071f"
|
|
blocks: []
|
|
related: []
|
|
created: 2026-05-10
|
|
updated: 2026-05-17
|
|
tags: []
|
|
---
|
|
|
|
## Contexto
|
|
|
|
Sub-issue derivado de 0071. **Rule of three cumplida hoy:** existe duplicacion masiva del panel de chat con `claude -p`:
|
|
|
|
| Consumidor | Archivos | LoC | Estado |
|
|
|---|---|---|---|
|
|
| `projects/osint_graph/apps/graph_explorer/chat.{cpp,h}` | 2 | 1241 + ~80 | Original |
|
|
| `projects/navegator/apps/navegator_dashboard/chat.{cpp,h}` | 2 | 1252 + ~80 | Copy+adapt reciente |
|
|
|
|
~2500 LoC duplicados con divergencia minima (env vars, CLI tool name, mutations counter). Bug fixes hay que aplicarlos en ambos. 3er consumidor potencial: `kanban` (agente para cards), `registry_dashboard` (agente para proposals).
|
|
|
|
## Objetivo
|
|
|
|
Funcion `claude_chat_panel` en `cpp/functions/core/` que encapsule:
|
|
- Spawn de `claude -p --dangerously-skip-permissions` (via `subprocess_streamer` de 0071f)
|
|
- Parser stream-json line-by-line (turn/role/tool_use/content)
|
|
- Render historial ImGui con bubbles user/assistant + tool_use detectado
|
|
- Input multi-linea con `Ctrl+Enter` send + history (flecha arriba/abajo)
|
|
- Persistencia opcional de conversacion en SQLite local
|
|
- Callback de mutations counter (la app recarga su BD cuando el agente la modifica)
|
|
|
|
## Dependencia previa
|
|
|
|
**0071f** (`subprocess_streamer`) DEBE estar mergeado. El chat_panel lo usa internamente. Sin ese building block, este issue no se aborda — se duplicaria la logica de spawn/pipes una vez mas.
|
|
|
|
## API propuesta
|
|
|
|
```cpp
|
|
namespace fn_core {
|
|
|
|
struct ClaudeChatConfig {
|
|
// Binario claude (default: "claude" en PATH; en WSL puede ser path absoluto a Windows).
|
|
std::string claude_bin = "claude";
|
|
|
|
// CLI helper que el agente invoca (ej. "gx-cli", "cdp-cli", "kanban-cli").
|
|
std::string cli_tool_name;
|
|
std::string cli_tool_dir; // dir donde vive el binario CLI helper
|
|
|
|
// Variables de entorno que el subproceso hereda (ej. GX_OPS_DB, NAVD_PROFILE_DIR).
|
|
std::map<std::string, std::string> env_vars;
|
|
|
|
// Path del .mcp.json que se le pasa a claude --mcp-config.
|
|
// Si vacio, no se pasa flag (usa el default del proyecto).
|
|
std::string mcp_config_path;
|
|
|
|
// Prompt sistema opcional (claude -p ... --append-system-prompt "...").
|
|
std::string system_prompt;
|
|
|
|
// Callback que la app implementa: devuelve N de mutaciones desde la ultima vez.
|
|
// El panel lo usa para mostrar "agent modificado N entities, recargar?" boton.
|
|
std::function<int()> mutations_counter;
|
|
|
|
// Path para persistencia local del historial (SQLite). Vacio = sin persistencia.
|
|
std::string history_db_path;
|
|
|
|
// Path de log de stream-json crudo (debug). Vacio = sin log.
|
|
std::string log_file_path;
|
|
};
|
|
|
|
struct ChatHandle; // opaco
|
|
|
|
ChatHandle* chat_create(const ClaudeChatConfig& cfg);
|
|
void chat_send(ChatHandle*, const char* user_text); // thread-safe
|
|
void chat_render(ChatHandle*, bool* p_open); // ImGui::Begin/End
|
|
void chat_clear_history(ChatHandle*);
|
|
void chat_destroy(ChatHandle*);
|
|
|
|
// Para apps que quieren leer el historial sin tocar la UI:
|
|
struct ChatMessage {
|
|
enum Role { User, Assistant, System, ToolUse, ToolResult } role;
|
|
std::string text;
|
|
std::string tool_name; // solo si role==ToolUse|ToolResult
|
|
int64_t timestamp_ms;
|
|
};
|
|
std::vector<ChatMessage> chat_history_snapshot(ChatHandle*);
|
|
|
|
} // namespace fn_core
|
|
```
|
|
|
|
## Migracion
|
|
|
|
Orden:
|
|
|
|
1. **Crear** `cpp/functions/core/claude_chat_panel.{h,cpp,md}` con la API arriba.
|
|
2. **Tests unitarios** en `cpp/apps/primitives_gallery/demos_chat.cpp`:
|
|
- chat_create con config minima → no crashea.
|
|
- mock subprocess (echo de JSON canned) → render muestra mensajes correctos.
|
|
- chat_send → input limpia, mensaje aparece como User.
|
|
3. **Migrar `graph_explorer`** primero (codigo mejor estructurado).
|
|
- `chat.{cpp,h}` reducido a un wrapper de ~30 LoC que construye `ClaudeChatConfig` con `GX_*` env vars.
|
|
- `app.md` añade `claude_chat_panel_cpp_core` a `uses_functions`.
|
|
4. **Migrar `navegator_dashboard`** segundo.
|
|
- Mismo wrapper, env vars `NAVD_*`.
|
|
5. **Verificacion manual**: en cada app, chat funciona igual que antes.
|
|
|
|
## Definicion de hecho
|
|
|
|
- `claude_chat_panel.{h,cpp,md}` registrado (`fn index`).
|
|
- Tests pasan (>= 3 casos en primitives_gallery).
|
|
- 2 consumidores migrados (chat.cpp en ambas apps reducido a ~30 LoC).
|
|
- Net LoC eliminadas: ~2400 (se reemplazan ~2500 LoC duplicadas por ~30 LoC config + ~700 LoC en cpp/functions/core).
|
|
- Tests visuales: golden image del panel renderizado en primitives_gallery.
|
|
|
|
## Anti-patrones
|
|
|
|
- "Mock everything" — la API NO acepta provider abstracto (Anthropic/OpenAI/...). Es claude-cli especifico. Si emerge un 4to consumidor con OpenAI, abrir issue separado.
|
|
- Async `co_await` / promises — callbacks bastan, KISS.
|
|
- Pre-extraer un `claude_subprocess_runner` separado del panel — lo hace 0071f (subprocess_streamer) generico.
|
|
- Hardcodear el render style en la funcion — exponer flags `cfg.render_compact / cfg.render_avatars` SOLO si un consumidor lo pide.
|
|
- Acoplar a un schema concreto (`entities`, `cards`, `tabs`) — la funcion solo conoce mensajes; las apps interpretan tool_use_calls a su manera.
|
|
|
|
## Riesgos
|
|
|
|
- **Divergencia oculta** entre las dos chat.cpp. Mitigacion: hacer un diff exhaustivo antes de extraer y documentar las diferencias en el .md.
|
|
- **Stream-json parser** es fragil ante cambios de formato de claude-cli. Mitigacion: tests con golden JSON capturado, version-pin del binario en docs.
|
|
- **History persistence** podria pisar conversaciones si dos apps usan el mismo path. Mitigacion: el path es config — cada app pasa su propio archivo.
|