Commit Graph

76 Commits

Author SHA1 Message Date
Jordan Diaz
79ec267aa6 Compactor: garantizar emparejamiento tool_use/tool_result (sesiones largas bloqueadas)
Las sesiones largas con DeepSeek quedaban bloqueadas permanentemente con
400 "Messages with role 'tool' must be a response to a preceding message
with 'tool_calls'": el paso de ultimo recurso del compactor colapsaba
assistants con tool_use a un string placeholder dejando huerfanos los
tool_result del user siguiente.

- compactor: paso de ultimo recurso pair-aware + _enforce_tool_pairing
  como invariante final (matching por IDs, ambas direcciones, repara
  tambien historiales ya corruptos persistidos).
- openai_adapter: _repair_tool_sequence como guard defensivo del contrato
  del proveedor (tool huerfano -> user; tool_call sin respuesta -> fuera),
  con warning para detectar regresiones.
- recent_messages: trim por presupuesto de tokens al persistir
  (AGENTIC_RECENT_MESSAGES_MAX_TOKENS, default 60k) sin cortar pares;
  cierra el crecimiento sin limite que empujaba al paso destructivo.
- tests/test_tool_pairing_real.py: 23 tests que importan el codigo REAL
  (a diferencia de los tests standalone existentes). Suite completa: 92 ok.

Verificado offline contra los recent_messages reales de la sesion
bloqueada en prod: 0 violaciones con presupuesto normal y agresivo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 19:08:53 +00:00
Jordan Diaz
43337e8554 Hardening: lock de sesion atomico, monitor off por defecto, fix DeepSeek reasoning-only
- session_lock: token uuid + compare-and-delete (Lua), TTL > timeout de
  ejecucion; abort solo limpia el lock tras cancelacion confirmada.
  Evita doble ejecucion concurrente sobre la misma sesion.
- monitor HTTP (puerto 4545) deshabilitado salvo MCP_MONITOR_ENABLED=true
  y atado a 127.0.0.1; no se acumula historial en memoria si esta off.
- DeepSeek/LiteLLM: turnos que llegan solo con reasoning_content (sin
  content ni tool_calls) ya no rompen la sesion (400 'Invalid assistant
  message') ni se pintan como 'pensando': se promueven a texto en el
  historial y en el snapshot persistido.
- litellm pinneado a ==1.80.0 (builds reproducibles).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 15:17:52 +00:00
Jordan Diaz
6a03fdf284 Harden DeepSeek agent: LiteLLM adapter, DSML/reasoning/embeddings/error fixes
- LiteLLMAdapter (subclasses OpenAIAdapter via _acreate hook): routes DeepSeek
  through LiteLLM. Opt-in AGENTIC_DEFAULT_MODEL_PROVIDER=litellm. A/B beat the
  hand-rolled adapter (0 DSML, 0 parse-fails). Defensive chunk.usage getattr,
  token-estimate usage fallback for billing, quiet litellm logs.
- DSML parser: tolerate single/multi fullwidth pipes, honor string="true/false"
  typed args (openai_adapter fallback when DeepSeek leaks tool calls as text).
- Thinking mode: capture and round-trip reasoning_content across turns.
- Embeddings: dedicated AGENTIC_EMBEDDINGS_API_KEY (DeepSeek has no embeddings);
  disable cleanly when unset to avoid per-turn 401.
- claude_format: friendly generic error messages to the chat, raw only in logs.
- acai agent max_tokens 4096->16384 (whole-file writes no longer truncate);
  system.md size-based edit policy; strict tools opt-in (off).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 14:49:48 +00:00
Jordan Diaz
e34a39e3bf fix(adapter): ejecutar tool calls que DeepSeek emite como texto DSML
Tercer modo de fallo del conector OpenAI (distinto de followup_mode y de
finish_reason=stop): DeepSeek a veces emite las tool calls en su formato interno
DSML (<||DSML||tool_calls>…, con U+FF5C) como TEXTO en el content, en vez de
como tool_calls nativos. El endpoint OpenAI no lo convierte, asi que el adapter
lo trataba como texto y el agente "se paraba" mostrando DSML inerte (0 tools).

Fix en OpenAIAdapter.stream: reutiliza el parser del claude_adapter
(_parse_xml_tool_calls / _TOOL_CALL_OPEN_RE). Acumula el content; si detecta el
inicio de un tool call en texto deja de emitirlo al usuario (DSML no debe verse);
al cerrar el turno, si no hubo tool_calls nativos, parsea el content y emite los
tool calls encontrados como tool_use para que el engine los ejecute.

Validado: el DSML real de la sesion (2x acai_grep) se parsea correctamente.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 20:15:49 +00:00
Jordan Diaz
d6b04e4122 fix(adapter): no perder tool_calls cuando DeepSeek cierra con finish_reason=stop
Sintoma (solo con el conector OpenAI): el agente anuncia la accion en texto
("Voy a crear los modulos…") y se PARA sin ejecutarla — 0 tools.

Causa: el stream del OpenAIAdapter solo emitia los tool_calls acumulados cuando
choice.finish_reason == "tool_calls". Pero DeepSeek (endpoint OpenAI) a veces
cierra el stream con finish_reason="stop" AUNQUE haya emitido tool_calls; en ese
caso caiamos en el branch else (end_turn) y los tool_calls acumulados se
descartaban. base.py solo ejecuta al recibir finish_reason="tool_use", asi que
nunca se ejecutaban. Con el adapter Claude (Anthropic) el finish_reason venia
distinto, por eso solo aparecia tras el cambio de conector.

Fix: disparar los tool_use SIEMPRE que haya tool_calls acumulados al cerrar el
stream, sea cual sea el finish_reason.

Validado: "crea un modulo…" ahora ejecuta acai_write + check_module y completa,
en vez de pararse tras anunciar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:55:40 +00:00
Jordan Diaz
96b4542918 fix(mcp): el read loop ya no muere con respuestas grandes (screenshots)
Sintoma: "el agente se para cuando hace acciones". MCPClient._read_loop lee las
respuestas JSON-RPC con stdout.readline(), cuyo StreamReader tenia buffer de 1MB.
Una respuesta llega en UNA linea; playwright__browser_take_screenshot({fullPage:
true}) devuelve la imagen en base64 en esa linea y supera el limite →
asyncio.LimitOverrunError → el except Exception mataba el read loop y dejaba la
sesion MCP inservible (los turnos siguientes ejecutaban 0 tools).

Fix en dos capas:
- MCP_STREAM_LIMIT=64MB en create_subprocess_exec(limit=...) — cubre cualquier
  screenshot real.
- Read loop tolerante: captura (ValueError, LimitOverrunError), descarta solo esa
  respuesta re-sincronizando el stream hasta el \n (_drain_until_newline) y sigue
  vivo, en vez de matar toda la sesion MCP.

Validado: navegar + screenshot fullPage + glob ejecuta las 4 tools sin "read loop
error" y sin colapsar el contexto.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 17:38:19 +00:00
Jordan Diaz
454b51b45d fix(agentic): DeepSeek llama tools de forma fiable (conector OpenAI + followup_mode)
Dos bugs encadenados impedían que el agente ejecutara tools (emitía los tool
calls como texto sin ejecutarlos, y degradaba el contexto):

1. Conector: el OpenAIAdapter pasaba los mensajes en formato Anthropic (bloques
   tool_use/tool_result) que la API OpenAI de DeepSeek rechaza, y defaulteaba el
   modelo a "gpt-4o". Añade `_to_openai_messages()` (assistant.tool_use →
   tool_calls; user.tool_result → role:tool con tool_call_id) y `_blocks_text()`,
   y usa `settings.default_model_id`. Con esto DeepSeek devuelve tool_calls
   nativos vía https://api.deepseek.com (endpoint OpenAI), sin parsear texto y
   sin la degradación que sufría el endpoint Anthropic-compat.

2. followup_mode: `_classify_followup_mode` marcaba como "transform" cualquier
   PRIMER mensaje que contuviera un marker ("resumen", "estructura", "busca",
   "adapta"…), y `_get_allowed_tools` devuelve [] en modo transform → el agente
   se quedaba SIN tools. Un follow-up no tiene sentido sin turno anterior, así
   que ahora solo se clasifica si hay task_history/recent_messages.

claude_adapter: parser DSML/DeepSeek para tool calls como texto (fallback del
endpoint Anthropic-compat, ya no es la vía principal).

Validado: el prompt de análisis de estilos ("Guarda un resumen…") ahora explora
los módulos y escribe docs/project-styles.md vía save_project_styles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 11:01:54 +00:00
Jordan Diaz
9854960c7c fix(mcp): límite LRU de sesiones MCP — evita degradación del contexto
Las sesiones MCP no se limpiaban (registry._sessions crecía sin límite); cada
una arranca 3 subprocesos stdio (acai-code, playwright+chromium, fetch) con sus
read-loops. Con varias vivas a la vez, las sesiones nuevas recibían cada vez
menos contexto/tools al modelo, hasta que el modelo dejaba de recibir tools y
emitía los tool calls como texto sin ejecutarlos (~300 input_tokens). Esto
degradaba el chat a lo largo del día hasta reiniciar el container.

Fix: MAX_ACTIVE_MCP_SESSIONS=2 con evicción LRU (touch last_used en
create/get_for_session, _evict_lru destruye las menos usadas). Seguro porque
send_message reconecta el MCP de una sesión evictada si vuelve a usarse.
Validado: 1 sesión viva era estable, 6 colapsaban; con cap=2, 7 sesiones
secuenciales se mantienen estables (40-115K tokens, tools OK).

Mitigación, no cura de fondo: el motivo por el que N managers vivos degradan
(probable: chromium de playwright) queda pendiente para subir el umbral.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 09:13:44 +00:00
Jordan Diaz
36318c61ea fix(chat): permitir abortar/preemptar ejecución en curso de una sesión
Antes, al parar el agente y mandar un mensaje nuevo, la ejecución previa
seguía viva reteniendo el session_lock: el mensaje nuevo recibía "busy" y el
stream mostraba la ejecución anterior. La tarea detached (create_task) no se
guardaba en ningún sitio y era imposible cancelarla.

- _running_executions: registro de la tarea asyncio por session_id.
- _cancel_running_execution(): cancela y espera a que libere el lock.
- send_message: preempt — un mensaje nuevo cancela la ejecución previa.
- _execute_and_persist: maneja CancelledError dejando la sesión en ACTIVE.
- POST /sessions/{id}/abort: cancela, cierra el stream SSE y limpia el lock.
- RedisStorage.clear_session_lock(): libera locks huérfanos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 17:50:46 +00:00
Jordan Diaz
c5c001468f Ajustes 2026-05-15 19:15:04 +00:00
Jordan Diaz
f7c6e65c0b ajustes 2026-05-15 08:36:39 +00:00
Jordan Diaz
b3ca343798 Ajustes de estructura 2026-05-10 21:27:47 +00:00
Jordan Diaz
5e64bbdfc8 Ajustes de estructura 2026-05-10 18:47:08 +00:00
Jordan Diaz
44cb956f95 Ajustes 2026-05-08 21:31:28 +00:00
Jordan Diaz
0dabba5442 ajustes en docs generales 2026-05-06 07:20:48 +00:00
Jordan Diaz
06ce51a9c1 Mas cosas 2026-05-06 07:07:57 +00:00
Jordan Diaz
8875cb29cb Ajustes de estructura 2026-04-29 15:30:21 +00:00
Jordan Diaz
3af875ed11 Ajustes de estructura 2026-04-28 20:25:09 +00:00
Jordan Diaz
6881d64a08 ajustes 2026-04-25 10:27:51 +00:00
Jordan Diaz
e84a36c83d mcp tablas 2026-04-25 08:51:17 +00:00
Jordan Diaz
62239cb0a5 ajustes coder 2026-04-21 16:55:37 +00:00
Jordan Diaz
362666295f Header y footer v1 2026-04-21 09:09:14 +00:00
Jordan Diaz
50c2076ebd libraries 2026-04-20 20:40:55 +00:00
Jordan Diaz
950d43f5d7 mcp remoto token 2026-04-20 11:10:51 +00:00
Jordan Diaz
41ebd39908 middleware 2026-04-19 09:18:48 +00:00
Jordan Diaz
2ac01acd61 cambios mcp remoto 2026-04-17 20:03:02 +00:00
Jordan Diaz
d41a94b57d Ajustes de translate 2026-04-17 10:24:43 +00:00
Jordan Diaz
c61a1465a8 Ajustes de max tokens 2026-04-14 21:31:14 +00:00
Jordan Diaz
469ff65052 Añadir completion + ajustes del chat 2026-04-14 07:12:50 +00:00
Jordan Diaz
15abc1eb4f toque en imagenes y filtrado en los esquemas 2026-04-12 14:45:50 +00:00
Jordan Diaz
f5b9e275c9 imagees del agente generadas y subidas con proxy server 2026-04-12 13:16:50 +00:00
Jordan Diaz
ca39cd2ccd tablas y delete module 2026-04-12 10:16:52 +00:00
Jordan Diaz
224ac2dad7 Control de modo editor/admin produccion/local 2026-04-10 16:52:00 +00:00
Jordan Diaz
0a8756c308 Añadido imagenes en records nuevos 2026-04-10 16:13:35 +00:00
Jordan Diaz
19efed84b7 compactor final 2026-04-09 21:41:11 +00:00
Jordan Diaz
237dc00379 nah 2026-04-09 20:46:03 +00:00
Jordan Diaz
4c73d848bb Primera fase context 2026-04-09 18:27:36 +00:00
Jordan Diaz
993e7d3000 Añadido el modo producción / test 2026-04-08 23:52:54 +00:00
Jordan Diaz
c1a29bbbf8 Selector de agentes 2026-04-07 10:57:40 +00:00
Jordan Diaz
38ac9cecdc MCP: bloquear escritura de records por accessList del usuario
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 21:52:13 +00:00
Jordan Diaz
5bfcee6918 get_web_url: forzar HTTP en forge + documentar ?pruebas=1 obligatorio
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 19:37:27 +00:00
Jordan Diaz
30a62d9a1d Tool get_web_url: devuelve URL correcta del proyecto para fetch/playwright
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 19:33:39 +00:00
Jordan Diaz
50ccc0e2a1 Docs: prohibir navegación a dominio de producción, solo usar ACAI_WEB_URL
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 19:30:49 +00:00
Jordan Diaz
fea9d2bd92 Fix docs: eliminar localhost:8080, usar URL real del proyecto
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 19:26:48 +00:00
Jordan Diaz
5b87676ef4 Fix permisos: appuser con UID 1001 (mismo que acai en container app)
El MCP server creaba archivos con UID 1000 que el server Python
(UID 1001) no podía modificar ni borrar. Ahora ambos containers
usan UID 1001, eliminando conflictos de permisos en volúmenes compartidos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 12:22:12 +00:00
Jordan Diaz
72da3b7659 Soporte base_url custom en Claude adapter (MiniMax Anthropic-compatible)
MiniMax tiene endpoint compatible con Anthropic API en
https://api.minimax.io/anthropic/v1. Nueva variable
AGENTIC_ANTHROPIC_BASE_URL para usarlo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:42:40 +00:00
Jordan Diaz
00c41fedb2 Soporte base_url custom en OpenAI adapter (MiniMax, DeepInfra, etc.)
Nueva variable AGENTIC_OPENAI_BASE_URL para proveedores compatibles
con OpenAI API (MiniMax, DeepInfra, Together, etc.).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:38:11 +00:00
Jordan Diaz
a86445f91a Fix historial: marcar como contexto pasado, no como nueva petición
El modelo repetía tareas anteriores porque el historial se
reconstruía como mensajes user/assistant que parecían peticiones
nuevas. Ahora el historial va como un bloque de contexto marcado
explícitamente con [HISTORIAL — NO ejecutar de nuevo].

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:30:13 +00:00
Jordan Diaz
a9fbd01b5d Fix Claude adapter: convertir mensajes OpenAI→Claude format
- role=tool → role=user con tool_result blocks
- assistant con tool_calls → assistant con tool_use blocks
- Merge mensajes consecutivos del mismo role (Claude requiere alternancia)
- Capturar input_tokens del evento message_start

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:22:35 +00:00
Jordan Diaz
184486b62b Context debug: guardar system_prompt + messages completos del último build
El endpoint /context-debug ahora devuelve full_context con el
system_prompt y messages exactos enviados al modelo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 09:19:16 +00:00