Commit Graph

105 Commits

Author SHA1 Message Date
Jordan Diaz
5198afedaa feat: rol auditor (solo lectura total) en el MCP + agente soporte
- roleCheck.js: isAuditor()/canEditContent(); canEditCode() tambien
  excluye auditor. Superficie editor/developer intacta (verificado
  contra el codigo pre-cambio en el test).
- records/tables/media/languages: tools de escritura gateadas con
  canEditContent(), orden de registro original preservado.
- agents/soporte: agente de evaluacion de incidencias en produccion,
  allowed_tools allowlist namespaceada (23 acai_code + 18 playwright,
  cero fetch), system.md con formato de informe obligatorio y trato
  del texto del cliente como dato no confiable.
- test/auditor-role.test.js: 22 tests de superficie exacta por rol.
2026-08-21 18:05:47 +00:00
Jordan Diaz
2ad2a6f87b docs: data-field-show descubrible desde el summary y el cheat-sheet
La seccion ya estaba escrita, pero el agente no llegaba a ella. Dos motivos:

- El summary del frontmatter y el parrafo de entrada de 01-builder-fields
  enumeran lo que cubre el doc y no mencionaban ni el agrupado en pestanas ni
  la visibilidad condicional. Ese summary no es decorativo: entra en el texto
  que se embebe (title + summary + primeros 2000 chars) y es lo que se ve en
  list_docs, asi que una seccion en el char 10000 sin rastro arriba es
  invisible para la busqueda semantica y para el que decide que doc abrir.

- 11b-rules-cheat-sheet es el doc de consulta rapida y ya tenia la linea de
  data-field-group; sin la hermana de data-field-show, quien mirase ahi
  concluia que las pestanas no se pueden condicionar.

Se anade la linea al cheat-sheet con la gramatica minima y los dos errores
tipicos (se referencia el nombre de variable y no el label; `campo=` es vacio).
2026-08-20 18:00:16 +00:00
Jordan Diaz
2afc2cdc34 docs: data-field-show para condicionar campos y pestanas del panel de modulos
El doc cubria data-field-group (repartir vars en pestanas) pero no habia forma
documentada de condicionar que se ve: los modulos con modos excluyentes
ensenaban al usuario todos los campos de todos los modos a la vez.

Se documentan los dos atributos nuevos (data-field-show en el campo,
data-field-group-show en una var del grupo), que llegan al builder.json por el
mismo mecanismo generico que data-field-group, con la tabla de la gramatica y
un ejemplo de las dos formas de uso.

Dos avisos que ahorran el error tipico: el campo se referencia por su NOMBRE DE
VARIABLE y no por su label (los acentos se borran, no se transliteran: "Titulo"
es `ttulo`), y `campo=` significa vacio, que es la clave de la primera opcion de
todo `list`.
2026-08-20 17:49:45 +00:00
Jordan Diaz
4835ff9467 docs: checkbox fuera de los tipos del builder y colors a las listas
El commit anterior (49b52b0) anadio un aviso de que `checkbox` no existe como
data-field-type, pero lo dejo listado como tipo valido en cuatro sitios: la
tabla de tipos de 01-builder-fields, su propio frontmatter `summary`, la tabla
de la cheat-sheet 11b y la lista del glosario. El agente consulta esas tablas
antes que el cuerpo del documento, asi que la contradiccion seguia viva:
escribe `checkbox`, el parser lo deja con type indefinido y funciones.php
descarta la variable en silencio. Era el origen del bug de tipos fantasma.

- `checkbox` fuera de las cuatro listas de data-field-type. El aviso se
  reescribe para remitir a `list` de dos opciones.
- OJO: `checkbox` SI existe como tipo de campo de TABLA del CMS
  (server/handlers/schema.py:98 y :284). Se conservan intactas sus menciones en
  05-tables-and-fields, 04-pages-and-records y la tabla de formato de datos de
  11b:88, que son otro vocabulario. El aviso lo dice explicitamente para que no
  se vuelva a borrar por error.
- `colors` entra en la tabla de 01, en la de 11b y en el glosario: tenia
  seccion propia pero no aparecia en ninguna lista, asi que quien consultaba la
  chuleta no sabia que existia. `colorpicker` se anade tambien al parrafo de
  intro de 01, que lo omitia.
- `corners` y `ratio` se dejan fuera de las tablas a proposito (decision del
  usuario: por ahora no se promocionan).
2026-08-13 20:05:21 +00:00
Jordan Diaz
49b52b0f9f docs: colors/colorpicker/corners/ratio y aviso de que checkbox no existe
01-builder-fields.md documentaba `checkbox` y `colorpicker` como tipos validos
—y usaba colorpicker en un ejemplo— pero el parser no los reconocia. Verificado
contra el parser real: ambos salian con `type` indefinido, y en ese caso
funciones.php hace `continue` y **descarta la variable en silencio**. O sea que
el doc mandaba al agente a escribir campos que nunca llegaban al builder.json.

En cambio no documentaba `colors`, `corners` ni `ratio`, que si funcionan.

- Se documentan los cuatro tipos reales, con el formato de valor de cada uno.
  `colors` es una paleta de N colores en una sola variable (JSON de pares
  nombre/color, nombres declarados en data-field-colors); `colorpicker` es un
  color suelto (hex plano).
- `colorpicker` pasa a existir de verdad: se anade al parser y al compilador
  (companion en el plugin maestro). Forge ya tenia widget para ambos —
  ColorsField y ColorField— asi que solo faltaba la pieza del parser.
- `checkbox` se marca explicitamente como inexistente, con la alternativa
  (`list` de dos opciones).
- set_module_example_data documenta el formato de ambos, que el agente no podia
  adivinar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 17:50:03 +00:00
Jordan Diaz
76221ee1d4 refactor: set_module_example_data escribe via el server Python
La tool llamaba a `action_ws=setStaticVars`, que hace un file_put_contents
del builder.json ENTERO en la web para cambiar una sola clave. Eso se
saltaba las dos garantias que protegen ese fichero: el bloqueo de escritura
de la API de ficheros (BLOCKED_GENERATED_FILENAMES en handlers/files.py) y
la allowlist de /api/modules/update-metadata.

El riesgo no era teorico. El builder.json es la unica memoria de que
variable vive en que columna de builder_custom: si una compilacion caia
entre la lectura y la escritura de setStaticVars, esta devolvia el mapeo
var->columna anterior encima del recien generado y el contenido guardado
quedaba colgado de la variable equivocada en TODAS las paginas que usan el
modulo.

Ahora delega en /api/modules/update-metadata, que ya es la via quirurgica
para la metadata y acepta staticVars en su allowlist (objeto, <=64KB,
<=200 claves).

Se aprovecha para quitar el volcado del schema y del payload completos por
consola en cada llamada.

Companion en el repo de Forge: compiler.js reenvia staticVars en cada
compilacion. Antes no lo mandaba, asi que el CMS lo tiraba al regenerar el
builder.json — solo 5 de 4870 modulos lo conservaban.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 15:21:16 +00:00
Jordan Diaz
f7e950694e feat: tools get_hook_entryparams / set_hook_entryparams
Completan la feature del endpoint /api/creator/hook-entryparams, que ya
estaba desplegado en el server (commit fe81765 del repo de Forge) pero sin
tools que lo usaran desde el agente.

Los entryParams son los parametros de entrada declarados de un hook global
({variable, value?, valueType?}), que se le pasan al invocarlo.
2026-08-12 11:32:17 +00:00
Jordan Diaz
bdee7665ec feat: el agente puede declarar y leer las cmsTables de un modulo
Las cmsTables (tablas del CMS cuyo contenido MUESTRA un modulo) ya se
definen desde Forge y desde el CMS legacy; faltaba que el agente las
entendiera.

- update_module_metadata acepta cmsTables. El endpoint del server ya la
  validaba, asi que solo habia que declararla en el schema Zod.
- get_module_config_vars la devuelve: sale gratis porque el handler ya
  resolvia el schema del modulo para uploadFields/varsMeta.
- Docs (01, 03, 09) con el matiz que mas se puede confundir: `tables` es
  donde viven los VALORES de las vars (siempre builder_custom, lo pone el
  compilador) y `cmsTables` es que contenido MUESTRA el modulo.

Para el agente lo util no es solo escribirlas: al recibirlas sabe donde
esta de verdad el contenido visible. Si le piden cambiar lo que muestra un
listado de noticias, los registros estan en esa tabla, no en las vars del
modulo.

Se documenta ademas que la metadata del builder.json es la UNICA parte
editable del fichero (y solo con esta tool): el resto lo regenera el
compilador en cada compilacion.
2026-08-12 11:32:07 +00:00
Jordan Diaz
5c011ab7ef refactor: create_or_update_record escribe via el server Python (un solo camino: enlace, contrasenas y metadatos de category) 2026-07-27 18:04:05 +00:00
Jordan Diaz
fb7ed09626 fix: create_or_update_record hashea los campos editor_password (nunca texto en claro) 2026-07-26 11:23:58 +00:00
Jordan Diaz
b9df5cbb54 feat: tool set_upload_info para editar info1-info5 de uploads (registros y vars de modulo) + docs 2026-07-25 23:08:52 +00:00
Jordan Diaz
6aea6c7005 docs: pestañas de vars con data-field-group + infoLabels de uploads en builder-fields 2026-07-25 16:12:46 +00:00
Jordan Diaz
89dac47c02 feat: tool update_module_metadata (label/description/onlyAdmin/MJML, id inmutable) 2026-07-25 16:12:46 +00:00
Jordan Diaz
5e61124c65 feat: guard de schemas de tablas en las file tools (redirige a las table tools) 2026-07-25 16:12:46 +00:00
Jordan Diaz
6dfedd07fc feat: warning cuando compute_cost cae a la tarifa fija de respaldo 2026-07-23 19:02:09 +00:00
Jordan Diaz
987e050539 fix MCP auth token refresh and disable legacy SSE 2026-07-18 10:03:12 +00:00
Jordan Diaz
9c3d9fb999 fix(mcp): las traducciones de vars de modulo van por NOMBRE de var, no columna fisica
Una sesion real del agente guardo title3/title6/title2 (columnas de
builder_custom segun varsMeta.fieldName, como decian las docs) y el front
no pintaba nada: el runtime traduce vars con t($record, $var) por nombre
de var. Corregidas descripciones de set/get_record_translations, docs
03/09/11b y ACAI_ENDPOINTS; ademas el puente PHP ahora rechaza titleN/
textN sobre builder_custom con error explicativo (guardrail).
2026-07-17 08:18:18 +00:00
Jordan Diaz
76a63ce4e4 docs(mcp): enlace traducible por idioma en set_record_translations
Path absoluto con prefijo (/en/...); CocoEnlace regenera las filas al
cambiar el enlace base. Actualizados tool description, ACAI_ENDPOINTS y
docs 04/09/11b.
2026-07-16 20:56:03 +00:00
Jordan Diaz
d46c204ed0 feat(mcp): tools de idiomas y traducciones
- Nueva categoria tools/languages: list_web_languages,
  get_record_translations y set_record_translations (registros, config,
  textos_generales y vars de modulo via builder_custom).
- Param lang opcional en list_table_records y get_record
  (options.translates de CocoDB).
- Docs actualizados (03/04/06/09/11b + ACAI_ENDPOINTS) con el modelo de
  cms_traducciones y los workflows de traduccion.
2026-07-16 20:09:47 +00:00
Jordan
d475845c27 Ajustes de links en docs 2026-06-22 14:04:28 +01:00
Jordan
941040d534 Ajustes de docs headfield 2026-06-22 14:01:45 +01:00
Jordan
037bc81936 Reportar modelo real en no-streaming + prewarm de mcp-server-fetch
- engine.py: process_message ahora incluye model/modelUsage en el dict de
  retorno (no solo en el evento SSE), para que el camino no-streaming
  (cronjobs -> _report_usage) reporte el modelo real a consumo_acaicode en
  vez de "unknown".
- Dockerfile: precalentar `uvx mcp-server-fetch` en build (como appuser) para
  que la cache de uv quede en la imagen y el MCP fetch no se quede sin arrancar
  por timeout en frio tras un rebuild.
- mcp.json: startup_timeout de fetch 15 -> 30s como margen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 13:20:51 +01:00
Jordan
882d578960 Reconexión: persistir 'executing' + objetivo al inicio del turno
Para que un reattach (tras recargar el frontend a mitad de turno) detecte que
hay un turno en curso, se persiste status=EXECUTING + current_objective ANTES
de la ejecución larga (el estado final lo sigue guardando el finally). Además
get_session expone el objetivo desde metadata mientras status==executing, ya
que current_task aún no está persistido durante el turno.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 16:44:36 +01:00
Jordan
651d61b096 P0 contexto: ventana por modelo + recuperación ante overflow + self-heal del catálogo
Que las conversaciones largas no se rompan ni gasten de más:

Ventana de contexto por modelo (antes: budget estático 120k/200k para todos):
- cost.resolve_context_window: lee context_length del catálogo OpenRouter/DeepSeek
  en Redis, con fallback a litellm. config.budget_for_window deriva el budget de
  la ventana real (window - max_output - reserve). build_context lo aplica por
  turno (param model_id) en vez del fijo de settings.
- Self-heal del catálogo OpenRouter: el admin panel lo cachea con TTL 1h y solo lo
  repuebla al abrir su ventana de IA → en runtime caducaba y se perdían ventana y
  precio. Ahora cost._get_catalog lo refresca solo (fetch público, mismo shape,
  cooldown 5min, TTL 24h). Arregla también el coste (caía al fijo).

Recuperación ante overflow:
- adapters.base.ContextOverflowError; openai_adapter traduce el error de
  context-length del proveedor (init e iteración del stream).
- base.py: retry proactivo que recompacta hasta caber en la ventana ANTES de
  llamar al LLM; si ni así cabe → error accionable (no rompe la sesión).
- engine.py: mensaje user-facing claro (modelo + ventana).

Tests: ventana/budget, self-heal (mockeado), overflow, y sesión REAL de Redis. 106 verdes.

evals/: harness para evaluar al agente acai-code (driver + README + resultados).
Comparativa kimi vs deepseek vs glm (deepseek-v4-pro high = mejor calidad/precio).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 13:48:19 +01:00
Jordan
9d11a59fb8 upload_record_image: aceptar ruta relativa del proyecto (sin base64)
Para una imagen local/pegada desde vscode: guardarla en una carpeta
sincronizada NO truncada (cms/uploads/chat/ o cms/uploads/generated/),
dejar que el sync la suba a test y pasar su RUTA RELATIVA como imageUrl.
El server lee los bytes de disco vía resolve_image_source — cero base64
por el contexto del modelo, cero URLs localhost inalcanzables.

- Validación relajada: además de http(s) y ruta absoluta, se acepta ruta
  relativa del proyecto (sin esquema, sin "..", <=512 chars, charset de
  ruta) → sigue rechazando data-URI/base64 crudo.
- Descripciones de upload_record_image / replace_record_image actualizadas
  con el flujo correcto.
- resolve_image_source y el aislamiento de entorno: sin cambios (la ruta
  relativa la resuelve por modo+stub, igual para chat y vscode).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 20:14:16 +01:00
Jordan
5dc2dbcf4a analyze/upload vía /api/image-bytes + MCP HTTP (vscode) forzado a test
Imágenes:
- analyze_image y upload resuelven los bytes por el endpoint Python
  /api/image-bytes (pythonGetBinary). analyze_image enruta los dominios
  forge (env ACAI_FORGE_DOMAIN) al endpoint en vez de fetch directo (que
  daba ECONNREFUSED 127.0.0.1 dentro del container).

Aislamiento de entorno (vscode = solo test):
- resolveCurrentModeOverride(): sesión MCP HTTP (mcpSessionId presente) →
  "local"; stdio (chat/cron) → ACAI_MODE_OVERRIDE de entorno. Lo usan los
  builders de headers (pythonServerClient, files/helpers) → toda tool del
  MCP HTTP manda X-Acai-Mode: local.
- httpServer.resolveProjectCredentials fuerza forceMode:"local" al resolver
  project-info → la sesión obtiene web_url/api_web_url forge-local y opera
  siempre contra test, nunca producción.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:11:50 +01:00
Jordan
5883473e92 Runtime IA: modelo dinámico, razonamiento, coste por modelo y visión nativa
- Resolución dinámica del modelo por sesión (model_resolver): override de
  usuario (metadata) → default global (Redis db 0 acai:config:ai:*) → fallback.
  Mapea a string litellm; LiteLLMAdapter respeta el modelo por request y
  enruta openrouter/* con OPENROUTER_API_KEY del entorno.
- Razonamiento: reasoning_effort por sesión en ModelConfig/AgentProfile,
  aplicado al agente y al planner.
- Coste: cost.py calcula por modelo (catálogo OpenRouter/DeepSeek en Redis →
  litellm → fijo) y emite tarifas + modelo usado en EXECUTION_COMPLETED.
- Visión nativa: imágenes como bloques image_url en el turno del usuario
  (TaskState.image_attachments → Context Engine → adapter), con persistencia
  en recent_messages y conteo de tokens de imagen (~1500).
- El turno no se pierde al cancelar: se persiste el mensaje del usuario + marca
  de interrupción para que un "vuelve a intentarlo" tenga contexto.
- Fix analyze_image: preservar el subdirectorio de usuario del chat-upload
  (basename descartaba "<user>/" → not found).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 14:47:55 +01:00
Jordan Diaz
4543300101 Fix upload_image_to_assets 404 en Forge (header Host) + guard data-URI
- saveFileBuilder (fileBuilder.js) hacía POST directo a viewer_functions.php
  sin header Host -> en Forge (api_web_url interno http://web:80) Apache
  servía el vhost por defecto -> 404. Ahora delega en
  AcaiHttpClient.postViewerAction, que resuelve api_web_url + Host:
  forge_host (igual que el resto de tools). Pasa credentials completo.
- upload_record_image: rechaza data-URI/base64 con error claro (antes
  derivaba el nombre del base64 -> "File name too long" en mcp_respond.php).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 09:29:17 +00:00
Jordan Diaz
9277862e56 read_doc: resolver docs por ACAI_PROJECT_DIR + knowledge load idempotente
- mcp-server _docsReader.js: resolveDocsDir → ACAI_DOCS_DIR /
  $ACAI_PROJECT_DIR/docs / /app/docs. Arregla DOC_NOT_FOUND en VSCode
  (HTTP MCP) y local; el .mcp.json ya inyecta ACAI_PROJECT_DIR
- routes.py: /knowledge/load idempotente — salta embeddings si el hash
  de contenido no cambió (clave Redis kbhash), para dispararlo libremente
  desde el botón de scaffold sin re-embeber

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 17:23:53 +00:00
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