@jossuealcala/madre 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +164 -0
- package/README.md +1 -1
- package/docs/REFERENCE.md +3 -2
- package/docs/SDK.md +19 -5
- package/docs/sdk/hello-module.mjs +8 -0
- package/package.json +1 -1
- package/public/app.js +414 -64
- package/public/es.js +246 -5
- package/public/index.html +4 -2
- package/public/styles.css +122 -14
- package/public/syntax.js +121 -0
- package/public/troubleshooting.js +100 -0
- package/src/adapters/codex.mjs +4 -1
- package/src/adapters/process.mjs +62 -15
- package/src/mcp/image-server.mjs +24 -2
- package/src/modules/image-studio.mjs +3 -2
- package/src/modules/index.mjs +23 -5
- package/src/modules/playwright.mjs +9 -5
- package/src/modules/sdk.mjs +15 -0
- package/src/outbound.mjs +7 -2
- package/src/room.mjs +12 -0
- package/src/runtime-detection.mjs +50 -0
- package/src/sentinel-errors.mjs +19 -1
- package/src/server.mjs +23 -12
- package/src/updates.mjs +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,170 @@ Todas las versiones publicadas de `@jossuealcala/madre`. Fechas en ISO.
|
|
|
4
4
|
|
|
5
5
|
Una versión se cierra cuando está en npm: hasta entonces su sección se llama **Sin publicar** y puede crecer. Cada versión publicada tiene exactamente una etiqueta `vX.Y.Z`, una release en GitHub y una sección aquí; el parche puede llegar a dos dígitos (`0.2.10`) antes de subir el menor. Ver `docs/ROADMAP.md` para el criterio de qué sube cada número.
|
|
6
6
|
|
|
7
|
+
## 0.5.0 · 2026-09-28
|
|
8
|
+
|
|
9
|
+
**Por qué menor y no parche.** Cambia a dónde sale lo que sale de tu máquina —el colector vive ahora bajo `madre.run`— y, sobre todo, **puede impedir que algo salga**: si tienes `OPENAI_BASE_URL` o `ANTHROPIC_BASE_URL` apuntando a un gateway, esos turnos quedan detenidos hasta que quites la variable o autorices ese endpoint con `PULSE_ALLOW_CUSTOM_AGENT_ENDPOINTS`. Es el arreglo correcto, pero un número de parche se lee como «actualiza sin mirar», y este no lo es. El ledger y la memoria de 0.4.x se siguen leyendo igual.
|
|
10
|
+
|
|
11
|
+
### Cambiar de conversación deja de recargar la página
|
|
12
|
+
|
|
13
|
+
- Abrir otra conversación hacía `window.location.reload()`: la sala entera otra vez — cada consulta, cada panel, el flujo de eventos desde cero. Era la forma bruta de asegurarse de que nada de la anterior quedara colgando.
|
|
14
|
+
- Ahora se repinta en sitio, y eso solo es seguro si **de verdad se suelta todo**: lo visto, los mensajes, las calificaciones, los turnos en vuelo, los planes, las estadísticas por agente, los archivos marcados, las condiciones registradas, los adjuntos pendientes, la cita de respuesta y el cursor del flujo. La lista está escrita —no adivinada— y hay una prueba que falla si alguien agrega estado por conversación y olvida soltarlo. Y el flujo se vuelve a apuntar, cerrando el anterior: dos abiertos a la vez entregarían cada evento dos veces.
|
|
15
|
+
- Si la sala no se puede releer, todavía recarga. Quedarse mostrando la conversación vieja sería peor que tardar.
|
|
16
|
+
|
|
17
|
+
### Dos cosas que se leían a medias
|
|
18
|
+
|
|
19
|
+
- En el menú de modos la cabecera se cortaba en `TEC…`, y lo que escondía era **el techo del agente**. Ahora dice `@CODEX · TECHO #4` y cabe en un renglón: la lista de abajo son literalmente los modos, así que «modo de este mensaje» no informaba de nada, y que la elección valga solo para este mensaje ya lo dice el globo del chip que abre el menú. La cabecera se queda con los dos datos que nada más lleva: quién, y hasta dónde.
|
|
20
|
+
- **LEER EL SDK** llevaba al archivo en GitHub. Va a `madre.run/sdk/` en español y `madre.run/en/sdk/` en inglés.
|
|
21
|
+
|
|
22
|
+
### La barra sigue diciendo en qué proyecto estás
|
|
23
|
+
|
|
24
|
+
- Dos nombres comparten ese rincón y solo uno te dice dónde estás. Los dos encogían por igual, así que una conversación con título largo dejaba el proyecto en **`pul…`** — una sala que dejó de decir qué sala es.
|
|
25
|
+
- Ahora el proyecto **no cede nunca** y la conversación es la que se recorta. Tiene dónde: su panel lleva el nombre entero. El proyecto no tiene otro sitio donde leerse.
|
|
26
|
+
- Y `EN VIVO` se partía en dos renglones cuando la barra se apretaba. Ya no.
|
|
27
|
+
|
|
28
|
+
### La fila de @madre cabe en un renglón
|
|
29
|
+
|
|
30
|
+
- Al elegir `@madre`, la fila sobre el campo se partía en dos: `PARA @MADRE` con una nota de tres cláusulas, el chip de modo, y un `modelo por defecto ▾` empujado abajo.
|
|
31
|
+
- **Ese chip no tenía nada que ofrecer.** `@madre` contesta con el modelo de chat que tenga Ollama, que se decide en la ficha de Ollama; aquí abría un menú vacío. Un agente sin modelos que elegir ya no trae selector — y era justo la pieza que rompía el renglón.
|
|
32
|
+
- La nota pasó de «memoria · contesta y le pregunta a la tripulación · nunca escribe» a **«memoria · nunca escribe»**. La frase entera sigue en su globo, que es donde estaba de todos modos.
|
|
33
|
+
- Y la fila ya no puede envolver. Si algún día vuelve a faltar espacio, lo que cede es la nota, con puntos suspensivos: un modo que no puedes leer es peor que una nota que no puedes.
|
|
34
|
+
|
|
35
|
+
### El cursor caía dentro de la píldora
|
|
36
|
+
|
|
37
|
+
- **Y en tema claro el código era ilegible.** Los colores del visor se eligieron para fósforo sobre negro: ese verde de las cadenas y ese amarillo de los números desaparecen sobre blanco. Ahora cada rol es una variable con su valor propio para cada fondo, como todo lo demás del tema — azul profundo, verde oscuro, ámbar tostado y magenta sobre claro.
|
|
38
|
+
- **La píldora respira.** Tenía el contorno pegado al texto, y el cursor quedaba encima del anillo en vez de después de él. Ahora lleva aire adentro y **lo paga con un margen negativo igual**: el siguiente carácter empieza exactamente donde empezaba. Esa cuenta también quedó en la prueba, porque relleno sin su compensación es la misma deriva por otro camino.
|
|
39
|
+
- **La píldora se comía el espacio de después.** El relleno lateral con margen negativo devuelve el sitio al *texto*, pero el fondo sí se pinta ahí: la píldora tapaba el espacio y la siguiente palabra llegaba pegada al borde. Y una sombra hacia afuera hace lo mismo, solo que más callada. Ahora crece **hacia arriba** —el relleno vertical no mueve un carácter— y su anillo se dibuja **hacia adentro**. Abraza sus letras, y el hueco que sigue es el espacio que tecleaste, visible.
|
|
40
|
+
- El corrector del navegador se queda encendido. Subraya las referencias, sí; perder la corrección en español y en inglés para ganar eso era mal negocio.
|
|
41
|
+
|
|
42
|
+
- Escribías una referencia y el cursor aparecía **en medio** de ella: `!public/brands.js|14`. No era el texto — era la capa que dibuja las píldoras encima del campo, desalineada del campo mismo. El cursor lo coloca el textarea con sus propias métricas; la píldora la dibujaba otra tipografía, y dos medidas distintas del mismo texto no pueden terminar en el mismo sitio.
|
|
43
|
+
- Dos causas, y la primera era gruesa. **Teclear `#2` dibujaba `#2 CREATE`**: siete caracteres que nadie escribió, así que todo lo que venía después quedaba siete lugares a la derecha de su propio cursor. La capa ahora dibuja solo lo que hay; el nombre del modo vive en el globo y en el chip sobre el campo, donde ya estaba.
|
|
44
|
+
- La segunda: cada píldora se re-tipografiaba —`font-family: mono`, `font-size: 13px`, `letter-spacing`, `font-weight`— mientras el campo real corre a 15px con la fuente de la interfaz. Ahora una píldora puede tomar el color que quiera y **ninguna métrica**: color, fondo, radio y sombra pintan sin mover nada.
|
|
45
|
+
- La regla quedó escrita en una prueba, porque una sola línea de CSS puede volver a romperlo: ninguna regla sobre el campo puede tocar familia, tamaño, peso, espaciado ni transformación, y el campo y su capa siguen compartiendo una sola declaración de forma.
|
|
46
|
+
|
|
47
|
+
### Tres detalles del menú de revisar
|
|
48
|
+
|
|
49
|
+
- **MADRE te ponía palabras en la boca, y en inglés.** Elegías un rango, elegías un agente, y el compositor se llenaba con `review this:` — una frase en inglés dentro de una sala en español, y peor: lo que escribías después caía *detrás* de ella y se leía como si alguien más lo hubiera empezado por ti. Ahora entra la referencia y el cursor. Nada más. Acabas de elegir un archivo, unas líneas y un agente: ya sabes qué quieres preguntar.
|
|
50
|
+
- **`@codex Codex` dice una cosa dos veces.** La arroba **es** el nombre. Al lado va solo lo que la arroba no te dice: de quién es el agente, que es el mismo que acaba de hablar, o que no tiene sesión. Pasaba en los dos menús —el del visor y el de responder desde una burbuja—; los dos.
|
|
51
|
+
- **La cabecera se cortaba a media palabra.** `REVISAR !public/app.js:24 CON` dejaba el verbo en una punta y su preposición en la otra, y el menú cortaba justo en «CON». Ahora son dos renglones: qué se manda, y debajo la pregunta. La referencia es una ruta y puede ser larga, así que envuelve en vez de empujar el menú o quedarse a medias.
|
|
52
|
+
|
|
53
|
+
### El árbol dice quién tocó qué, y el visor lee por lo que el archivo es
|
|
54
|
+
|
|
55
|
+
- Y el panel de archivos se rompió al estrenar ese punto: `no se pudo listar: Cannot access 'touched' before initialization`. La misma zona muerta temporal que ayer — una `const` declarada al fondo del archivo, leída por una función que corre durante el arranque. **Dos veces el mismo error en dos días**, y las dos veces con la suite en verde: el panel **atrapa sus propios fallos y los dibuja como una línea de texto**, así que nada lanzaba y la prueba de humo nunca abría el árbol. Ahora arranca con el panel abierto y **afirma que pintó**, no solo que nada explotó. Comprobado: con el error reintroducido, falla.
|
|
56
|
+
|
|
57
|
+
- **Un punto en el color del agente**, junto a cada archivo que un agente escribió en esta sala. Sale de lo que la sala ya registró —los artefactos de un permiso de creación y los archivos que cambió un turno de CONTROL—: no se guarda nada nuevo y no se deduce nada del disco. Un archivo que MADRE no vio cambiar no lleva marca, que es lo honesto.
|
|
58
|
+
- **El visor resalta por extensión**, con un resaltador propio de doscientas líneas: JavaScript, JSON, CSS, HTML, Markdown, Python, Shell, Config y SQL. Cero dependencias, como todo lo demás. La regla que lo mantiene honesto es que **nunca reescribe el archivo**: devuelve tramos sobre los mismos caracteres, en el mismo orden, así que lo que ves y lo que hay en disco son los mismos bytes — y una selección que le mandas a un agente es el archivo, no una versión dibujada de él. La prueba lo sostiene sobre nueve familias. Lo que no reconoce se queda en texto plano en vez de adivinarse.
|
|
59
|
+
- **Y preguntarle a un agente sobre un trozo dejó de ser un secreto.** `REVIEW WITH` existía desde siempre, pero aparecía solo *después* de seleccionar, y nadie adivina que un número de línea se puede clicar. Ahora, mientras no hay selección, el visor dice cómo: «clic en un número de línea para seleccionar · shift para un rango». Y dice qué está leyendo.
|
|
60
|
+
|
|
61
|
+
### `/module` sin `#2` contesta en la sala
|
|
62
|
+
|
|
63
|
+
- Escribir `/module` sin CREATE solo lanzaba un aviso que se desvanecía. Un rechazo que solo parpadea es un rechazo que la sala olvida. Ahora la respuesta cae **en el hilo**, donde caen todas las decisiones de la sala, dice que tu petición sigue en el compositor, y trae el botón que arma el modo. **El comando pide; el clic concede** — escribir un comando nunca arma un permiso, y la prueba lo sostiene.
|
|
64
|
+
|
|
65
|
+
### El briefing ya no sale por una puerta que nadie nombró
|
|
66
|
+
|
|
67
|
+
- Codex y Claude heredan del shell las direcciones base de sus APIs. Eso permite usar gateways legítimos, pero también hizo que una sala que mostraba «OpenAI» o «Anthropic» pudiera enviar el briefing a otro host sin decirlo. MADRE ahora enseña el destino efectivo en CONEXIONES y en QUÉ SALIÓ DE ESTA MÁQUINA; solo conserva el hostname, nunca la ruta, la consulta ni una credencial.
|
|
68
|
+
- Un endpoint personalizado queda bloqueado antes de armar o entregar el briefing. Para volver al servicio oficial basta con quitar `OPENAI_BASE_URL` o `ANTHROPIC_BASE_URL`; quien sí verificó su gateway puede autorizar por agente con `PULSE_ALLOW_CUSTOM_AGENT_ENDPOINTS=codex` o `=claude`. Bedrock, Vertex AI y Foundry se nombran como los proveedores explícitos que son.
|
|
69
|
+
- MU/TH/UR reconoce el 403 que exige unirse a Telegram como **MU-055**: explica que viene del gateway, no de MADRE ni del proveedor oficial, y no recomienda suscribirse ni entregar credenciales.
|
|
70
|
+
- Codex ya no se reduce a «salió con código 1» cuando su JSON trae `error` o `turn.failed`: la sala conserva el motivo que el propio CLI reportó. Si no trae ninguno, se mantiene el código de salida como último dato honesto.
|
|
71
|
+
|
|
72
|
+
### El SDK deja de tener trampas
|
|
73
|
+
|
|
74
|
+
Antes de que alguien pueda pedirle a un agente «hazme un módulo», el contrato tiene que perdonar los tres errores que cualquiera comete. Medí ocho módulos —uno bueno y siete rotos— y seis de los siete ya contestaban con precisión. Estos eran los que no.
|
|
75
|
+
|
|
76
|
+
- **`/module` ya es la entrada explícita para construir.** `/module <qué debe hacer>` requiere `#2 CREATE`: si falta, MADRE lo pide y conserva el texto en el compositor; nunca se concede el permiso solo por escribir un comando. Con `#2` activo, conserva el agente elegido y convierte la petición en el contrato de un único `<id>.module.mjs` que el humano revisa e instala desde su tarjeta. Mientras se escribe, el compositor cambia a morado MADRE eléctrico con una transformación breve de píxeles; el estado queda limpio y legible después de la transición, y respeta movimiento reducido. La zona de escritura reserva ahora cinco píxeles laterales para que el contorno del chip no se recorte.
|
|
77
|
+
- **Un campo mal escrito pasaba en silencio.** Escribe `sumary` en vez de `summary` y el módulo instala sin una queja, con la ficha vacía y sin manera de saber por qué. Con treinta y dos campos opcionales ese es *el* error que va a cometer todo el mundo, un agente incluido. Ahora MADRE dice qué campos no conoce — **dice, no rechaza**: un módulo escrito para una MADRE más nueva puede traer campos que esta todavía no aprendió, y eso no debe impedirle cargar.
|
|
78
|
+
- La advertencia salía al subir un archivo desde MODULES, pero no al instalar el mismo archivo desde la tarjeta que deja un agente en la conversación. Las dos entradas pasan por el mismo verificador y ahora también terminan en la misma advertencia; `sumary` ya no vuelve a quedar en silencio según quién escribió el archivo.
|
|
79
|
+
- `install` figuraba como campo entendido aunque `defineModule` no hacía nada con él. El contrato documentado pone esa línea en el resultado de `status(ctx)` y reserva `installCommand` para el comando confirmado; el campo superior fantasma salió de la lista y ahora se señala como desconocido en vez de prometer un efecto que no existe.
|
|
80
|
+
- **DESINSTALAR ya vive en la tarjeta del módulo.** El backend protegía los módulos integrados y la acción existía escondida en la tarjeta `</>`, pero la ficha `DEV` no la ofrecía. Ahora cada módulo externo confirma su ruta y puede quitarse ahí mismo; borra su archivo y el registro que recuerda de dónde actualizarlo, recarga el registro y deja intactos los módulos que MADRE trae de fábrica.
|
|
81
|
+
- **La documentación prometía un `import` que el instalador rechaza.** `SDK.md` decía que podías `import { defineModule } from '@jossuealcala/madre/sdk'`. El paquete lo exporta bien, pero MADRE copia tu archivo a una carpeta aparte para revisarlo antes de instalarlo —así sabe qué es sin que corra donde vive— y ahí no hay `node_modules`: ningún paquete resuelve. Ahora la guía enseña la forma que sí funciona, que ya existía: exporta una función y **MADRE te entrega el SDK**.
|
|
82
|
+
- **Y la trampa que solo le pasa a un agente.** Los módulos que vienen con MADRE escriben `import { defineModule } from './sdk.mjs'`, porque viven dentro del paquete. Un agente al que le pides un módulo lee `ripley.mjs` antes que cualquier documento y copia esa línea — que es correcta ahí e imposible fuera. Node contestaba solo «no encuentro el archivo», que manda a buscar lo que no es. Ahora el error termina diciendo qué hacer en su lugar, y el ejemplo y la guía lo advierten con esas palabras.
|
|
83
|
+
|
|
84
|
+
## 0.4.1 · 2026-09-27
|
|
85
|
+
|
|
86
|
+
### MADRE tiene casa: madre.run
|
|
87
|
+
|
|
88
|
+
- **Y lo que trae una versión se lee en esa casa.** `QUÉ TRAE 0.4.2 ↗` llevaba a la página de releases de GitHub, que está escrita para quien ya trabaja ahí; quien aprieta ese botón solo quería saber qué cambió. Ahora va a `madre.run/cambios/` en español y a `madre.run/en/changelog/` en inglés, con la versión en el ancla (`#v0.4.2`) para que la página lo deje parado en la sección que le toca.
|
|
89
|
+
|
|
90
|
+
- El colector de errores vivía en `madre-reports.jossue-alcala-o.workers.dev`, y **ese host se le enseña al usuario** en el registro de salidas del núcleo. Un subdominio de `workers.dev` con un nombre de cuenta pegado parece un endpoint improvisado; `madre.run` parece el producto. La dirección es parte de lo que se le pide confiar a alguien.
|
|
91
|
+
- **La dirección vieja sigue contestando, y tiene que seguir.** Cada sala en 0.4.0 y anteriores la lleva compilada dentro: apagarla detendría sus reportes en silencio. Queda escrita en el código y en la prueba, en vez de desaparecer del registro.
|
|
92
|
+
- **Dos puertas para cada reporte humano, siempre.** Un fallo con traza pertenece a GitHub, donde se deduplica y la respuesta le sirve al siguiente; pero pedirle una cuenta a quien llegó de un grupo en español para decir «no me detecta Codex» es perderlo. En español manda el formulario y el issue queda a un clic; en inglés, al revés. Nadie choca con un muro de login con un reporte en la mano.
|
|
93
|
+
- Máquina y humano van a puertas distintas a propósito: `madre.run/v1/reports` para el centinela, `/reportar` y `/report` para personas. Mezclarlos ensucia lo que el autor lee.
|
|
94
|
+
- Y el logo —el del README y la marca de la barra— apunta a la casa del producto en vez de al sitio personal del autor. La atribución se queda donde corresponde, al pie del README.
|
|
95
|
+
- De paso salió un defecto que yo mismo introduje al cablear esto: un identificador de reporte inexistente devolvía 200, porque el formulario no necesita que el reporte exista para construirse. Ahora ninguna de las dos puertas se ofrece para algo que no está.
|
|
96
|
+
|
|
97
|
+
### Image Studio buscaba la llave donde MADRE no la guarda
|
|
98
|
+
|
|
99
|
+
- Y en la misma ficha, una línea estaba bajo la etiqueta equivocada: «se engancha solo dentro de un permiso de creación con el alcance de imagen encendido» no es algo que el módulo *escriba*, es una condición para que se enganche siquiera. Pasó a **NECESITA**, y de paso quedó redactada como requisito: «un permiso de creación con el alcance de imagen encendido; solo ahí se engancha» — bajo *necesita*, la frase anterior no concordaba.
|
|
100
|
+
|
|
101
|
+
- Cuando pegas una llave de Gemini en ⚙ CONEXIONES, MADRE la escribe en **`~/.gemini/.env`**, que es donde ese CLI la lee. Image Studio miraba tres sitios: `GEMINI_API_KEY`, `GOOGLE_API_KEY` y el llavero de macOS. **Ese archivo no estaba en la lista.** La sala guardaba una llave en un sitio donde después se negaba a mirar, y la ficha contestaba «no se encontró llave de Gemini» por una llave que acababa de escribir ella misma.
|
|
102
|
+
- **En Linux era total:** la rama del llavero es solo de macOS, así que `~/.gemini/.env` era el único lugar posible — y nunca se leía. Image Studio no podía encontrar una llave en Linux salvo exportando una variable a mano.
|
|
103
|
+
- Ahora el orden es: lo que se le dio a este proceso, después el archivo que el CLI lee en cada arranque —y que MADRE escribe—, y al final el llavero, que solo existe si el propio inicio de sesión del CLI lo puso ahí. Entiende la forma citada y la de `export`, y una asignación vacía no es una llave.
|
|
104
|
+
|
|
105
|
+
### Los remedios: prosa en español y un botón donde había un comando
|
|
106
|
+
|
|
107
|
+
- **44 de 53 condiciones explicaban su remedio en inglés.** No los comandos —un comando no se traduce— sino los **comentarios** que los acompañan: 145 líneas de prosa, y la mitad nombrando botones. `# ⚙ CONNECTIONS → Gemini → GENERATE IMAGES → SAVE` en una sala donde esos botones se llaman **CONEXIONES**, **GENERA IMÁGENES** y **GUARDAR**. El remedio mandaba a buscar cosas que no existen con ese nombre.
|
|
108
|
+
- Los 80 comentarios distintos están en español, con los nombres que el usuario de verdad ve. La guardia del catálogo ahora los recorre como recorre el título y el diagnóstico de cada condición — y aprendió que algunos se **componen** al cargar (el helper que arma un `export` los escribe), así que pregunta también a las condiciones y no solo al código fuente.
|
|
109
|
+
- **Ocho remedios eran un comando solo porque nadie había cableado el botón.** Firmar un agente, instalar uno, encender un alcance: MADRE ya hace las tres desde CONEXIONES. Esas condiciones ahora declaran `solvedIn: 'connections'` y su ficha abre el panel **en el agente del que se trata**, en vez de entregar una línea para pegar. Los comandos se quedan debajo, para otra máquina o para quien prefiera teclear.
|
|
110
|
+
- Lo que sigue en terminal, sigue a propósito: los siete remedios que tocan el perfil de tu shell. MADRE no debe editarte el `.zshrc` — ahí vive configuración que no escribió ella.
|
|
111
|
+
|
|
112
|
+
### El registro, legible: un icono que no estorba y un archivo que se manda
|
|
113
|
+
|
|
114
|
+
- **COPIAR se sentaba encima del comando que ofrecía.** Era una palabra flotando en la esquina del bloque, y cualquier comando largo le pasaba por debajo. Ahora es un icono de dos hojas, con su sitio reservado: el comando ya no corre bajo nada, y al copiar el icono se enciende en vez de convertirse en otra palabra.
|
|
115
|
+
- **La `×` de descartar se caía al renglón de abajo.** La fila del registro tenía cuatro columnas y al agregarle la salida quedaron cinco cosas. Ahora son cinco columnas, con aire entre ellas.
|
|
116
|
+
- **DESCARGAR EL REGISTRO.** Un CSV con una fila por condición: cuándo, qué agente, **el código de soporte**, la condición y el error. Se abre en cualquier hoja de cálculo y lleva BOM, para que los acentos no se rompan al abrirlo. Quien lo reciba no necesita tener MADRE abierta para leerlo.
|
|
117
|
+
|
|
118
|
+
### Entrar a NOSTROMO no puede dejarte encima de la salida
|
|
119
|
+
|
|
120
|
+
- Y el arreglo dejó su propia marca: al darle el foco al mapa, el navegador le dibujaba **un marco azul alrededor de toda la constelación**. Al mapa no se llega con Tab —está fuera del orden de tabulación a propósito—, así que ese anillo no le señalaba nada a nadie. Quitado; no se esconde ninguna pista al hacerlo, porque no hay adónde tabular.
|
|
121
|
+
|
|
122
|
+
- Escribías la designación, dabas Enter, y el foco quedaba en **SALIR**: el siguiente Enter te echaba de donde acababas de entrar. Un diálogo le entrega el foco a su primer hijo enfocable, y con COLD, PREGUNTAR y RECENTRAR ocultos, ese primero era la salida. Ahora lo toma el mapa, que es a lo que fuiste.
|
|
123
|
+
|
|
124
|
+
### El registro de errores: un código que se dicta, y una salida por fila
|
|
125
|
+
|
|
126
|
+
- **El badge de MU/TH/UR subía y nunca bajaba.** Cada condición registrada se reconstruye del ledger en cada carga, y no había forma de descartar ninguna: veintitantas alertas encima del botón, todas de cosas ya resueltas. Ahora cada fila del registro tiene su `×`, y cuando hay varias, un **DESCARTAR LOS N** de una vez.
|
|
127
|
+
- Descartar **no borra nada**: el ledger es historia y no se reescribe. Es una preferencia de lectura y vive donde viven las preferencias de lectura — este navegador —, atada a lo que el registro *es* (hora, agente, primera línea), así que la misma entrada reproducida del ledger en la siguiente carga sigue descartada. Y hay vuelta atrás: **VER LOS N DESCARTADOS** los trae todos de regreso.
|
|
128
|
+
- El saludo de MU/TH/UR y el badge cuentan ahora lo mismo: lo que sigue abierto.
|
|
129
|
+
- **Cada condición tiene su código de soporte**, `MU-001` a `MU-054`. Sale en la ficha y es lo que el botón del registro enseña, porque es lo que una persona dicta por teléfono cuando pide ayuda. El código vive **en la condición**, no en su posición en la lista: insertar una nueva en medio no renumera a las demás, que es justo lo que arruinaría un código de soporte. Con su prueba: todas lo tienen, ninguna lo repite, y un módulo que declara una condición declara su código con ella.
|
|
130
|
+
|
|
131
|
+
### Un número no es una razón
|
|
132
|
+
|
|
133
|
+
- `Claude exited with code 143.` se lee como que el agente se rompió. No se rompió: **143 es 128+15**, un CLI que atendió un SIGTERM y se despidió. Algo lo **detuvo** — y eso manda a buscar a un lugar completamente distinto que una caída.
|
|
134
|
+
- MADRE además **ignoraba la señal**: `child.on('close', (code) => …)` tiene un segundo argumento que dice qué señal cerró el proceso, y no se leía. Un proceso matado por señal llegaba como `código null`.
|
|
135
|
+
- Ahora las dos formas se dicen con palabras: SIGTERM es «algo le pidió que se fuera: PARAR TODO, un tiempo límite, o la sala cerrándose»; SIGKILL, «matado en seco, casi siempre la máquina quedándose sin memoria»; SIGINT, «un Ctrl+C le llegó». Un fallo ordinario conserva su número, que ahí sí es lo que diagnostica.
|
|
136
|
+
|
|
137
|
+
### PLAYWRIGHT nunca conectó, y npm se estaba comiendo las banderas
|
|
138
|
+
|
|
139
|
+
- Pedirle a un agente que use el navegador contestaba `CONNECTION_CLOSED: "Connection closed"`. La causa: MADRE lanza el servidor con `npx --no @playwright/mcp --headless --isolated …` y **npm se queda con esas banderas como configuración suya** —`Unknown cli config "--headless"`— y le entrega al servidor solo los valores sueltos. El servidor muere con `too many arguments. Expected 0 arguments but got 4` antes de hablar una palabra de MCP. Falta un `--` entre el paquete y sus banderas. Probado: sin el separador el servidor muere con código 1; con él contesta el saludo MCP.
|
|
140
|
+
- Debajo había un segundo: **el servidor y su navegador son dos cosas distintas**, y la ficha solo comprobaba el paquete. Un PLAYWRIGHT «encendido · chromium» puede no tener navegador, y entonces cada herramienta contesta `Browser "chrome-for-testing" is not installed`. El remedio que mostraba MADRE además ya no era el correcto: hoy el navegador lo baja el propio servidor (`npx @playwright/mcp install-browser chrome-for-testing`), no el CLI de playwright.
|
|
141
|
+
- Y un tercero al ir a traducir lo anterior: **las condiciones que declara un módulo no estaban en el catálogo español**. Viven en el archivo del módulo, no en `troubleshooting.js`, así que la guardia de las 51 nunca las vio y PLAYWRIGHT se explicaba en inglés dentro de una sala en español. La guardia ahora lee también los módulos.
|
|
142
|
+
|
|
143
|
+
### Un CLI que se actualiza solo debajo de una sala abierta
|
|
144
|
+
|
|
145
|
+
- En un QA salió `spawn /Applications/ChatGPT.app/Contents/Resources/codex ENOENT` con el binario **existiendo y siendo ejecutable**. La app de ChatGPT había actualizado su Codex a media sesión —de `0.154.0-alpha.6.2` a `0.155.0-alpha.9.2`— y MADRE seguía apuntando al que ya no estaba. Reabrir la sala encuentra el nuevo; sin saber eso, parece que el producto se rompió.
|
|
146
|
+
- Lo que hacía ilegible ese momento: **`spawn` falla de dos maneras y solo una estaba explicada.** La asíncrona levanta `error` en el proceso hijo y ahí sí había una frase con qué hacer. La síncrona **lanza antes de que exista un hijo al que escucharle**, así que se saltaba el manejador entero y a la sala llegaba el error crudo de node. Ahora las dos pasan por la misma explicación, y nombra la causa que nadie puede adivinar: un CLI que se actualiza solo puede moverse debajo de una sala ya abierta.
|
|
147
|
+
|
|
148
|
+
### Un aviso no es una causa
|
|
149
|
+
|
|
150
|
+
- Un usuario reportó que MADRE «solo funciona con APIs»: tenía Codex y Claude con sus planes, los dos con sesión iniciada, y ningún turno contestaba. Lo que leía era, en Claude, un aviso sobre `ANTHROPIC_API_KEY`; en Codex, la línea `Reading additional input from stdin...`. Ni una de las dos cosas era la razón.
|
|
151
|
+
- **El defecto estaba en cómo MADRE reporta un turno fallido.** Cuando un turno no producía respuesta, el mensaje era `stderr.trim() || …` — lo que hubiera quedado en stderr, y el `||` **descartaba lo único que diagnostica**: el código de salida, o el hecho de que el CLI salió limpio sin decir nada. Así un aviso sobre un ajuste que no venía al caso se volvía «la causa», y la causa real no llegaba a nadie. Para Codex era peor: su parser nunca devuelve un error propio, así que stderr era literalmente todo lo que quedaba.
|
|
152
|
+
- Ahora el mensaje dice primero **qué pasó** —`salió con código 3`, o `salió limpio sin respuesta`— y después, separado y etiquetado, **lo que el CLI imprimió**. Nunca uno disfrazado del otro. El código de salida viaja en el error para el centinela.
|
|
153
|
+
- Y MU/TH/UR aprendió las dos condiciones. La de Claude explica lo que el usuario no podía saber: MADRE le entrega al CLI el entorno tal como está esta computadora, así que una `ANTHROPIC_API_KEY` exportada en tu perfil **gana sobre el plan de claude.ai** y el turno se le cobra a la API. La de Codex dice lo contrario de lo que parece: esa línea es inofensiva, MADRE lo corre con stdin cerrado, y si el turno falló la razón es la línea de arriba.
|
|
154
|
+
|
|
155
|
+
### Lo que encontró quien la probó
|
|
156
|
+
|
|
157
|
+
- **`PULSE_LANGUAGE` no cambiaba la interfaz** — y el defecto era peor que eso. El servidor sí la respetaba al arrancar y también al presionar el botón; pero `/api/state`, que es de donde la página aprende qué idioma habla esta máquina, **volvía a leer el archivo de configuración** en vez de preguntar por el idioma ya resuelto. Con la variable puesta, la sala quedaba **partida**: los textos del servidor en inglés dentro de una interfaz en español. Una sala partida es peor que cualquiera de los dos idiomas. Ahora la página recibe el idioma que la sala está hablando, no el que está escrito en el archivo, y no hay una segunda expresión que pueda volver a discrepar.
|
|
158
|
+
- **«1 ARCHIVOS».** Un archivo creado no son archivos. El defecto venía del inglés —`created · 1 files` decía lo mismo— así que no era de la traducción: estaba ahí desde que existen los artefactos.
|
|
159
|
+
- **EYECAT hablaba inglés.** Sus dos hallazgos —«una nota que sus propias fuentes no sostienen» y «dos memorias que no pueden ser ciertas a la vez»— nunca se envolvieron. Eran frases enteras, no una palabra suelta.
|
|
160
|
+
- Y al buscar esa, apareció una cuarta: **la severidad de cada condición de MU/TH/UR se imprimía cruda**. Ocho fichas diciendo `blocking`, `degraded`, `transient`, `informational` en una pantalla por lo demás en español.
|
|
161
|
+
|
|
162
|
+
### La tercera vez que pasa lo mismo
|
|
163
|
+
|
|
164
|
+
- Las clases de memoria, el nivel de un aviso y ahora la severidad de una condición: las tres son **un id en el catálogo y una palabra en la pantalla**, y las tres se imprimieron crudas. La guardia del catálogo no puede verlas por construcción — nunca pasan por `t()`, así que no hay llave que falte. Ahora hay una guardia para **la forma**: falla si un `.kind`, `.level`, `.severity`, `.status` o `.stage` se escribe directo en un nodo, y exige que los tres mapas sigan declarados entrada por entrada, porque calcular la llave los esconde de las dos guardias a la vez.
|
|
165
|
+
- De paso salió un homónimo que un catálogo con llave en inglés no puede sostener: `FIXED` ya era **FIJO** —la parte del prompt que no cambia— y la severidad `fixed` quiere decir **resuelta**. La ficha en inglés pasó a decir `ALREADY FIXED`, que es más claro en los dos idiomas.
|
|
166
|
+
|
|
167
|
+
### No es un defecto
|
|
168
|
+
|
|
169
|
+
- **Los agentes contestan en tu idioma, no en el del mensaje.** MADRE sí pide el idioma: el bloque `style` del briefing les dice «escribe a la sala en el idioma en que escribe el humano» (`src/room/prompt.mjs:86`). Lo que pasa es que tus instrucciones globales —tu `CLAUDE.md`, tu `AGENTS.md`— **ganan sobre el briefing**, y así debe ser: son tuyas y son permanentes. Es la consecuencia directa de que MADRE use tus sesiones tal cual, que es justo lo que promete.
|
|
170
|
+
|
|
7
171
|
## 0.4.0 · 2026-09-26
|
|
8
172
|
|
|
9
173
|
### Actualizar de verdad, y una puerta para la comunidad
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://
|
|
2
|
+
<a href="https://madre.run"><img src="https://raw.githubusercontent.com/jossuealcacao-exe/madre/main/docs/madre-banner.svg" alt="MADRE · MU/TH/UR 6000 · INTERFACE 2037" width="100%"></a>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
package/docs/REFERENCE.md
CHANGED
|
@@ -197,7 +197,7 @@ Cada módulo es un archivo. Los tuyos van en `~/.pulse/modules/` (todas las sala
|
|
|
197
197
|
## LO QUE SALE DE LA MÁQUINA
|
|
198
198
|
|
|
199
199
|
- **Nada por sí solo.** MADRE no tiene nube, cuenta ni backend. No guarda credenciales.
|
|
200
|
-
- **Lo que un agente lee, viaja a su proveedor.** Codex a OpenAI, Claude Code a Anthropic, Gemini CLI a Google, OpenCode a quien tenga configurado. Aplican
|
|
200
|
+
- **Lo que un agente lee, viaja a su proveedor.** Codex a OpenAI, Claude Code a Anthropic, Gemini CLI a Google, OpenCode a quien tenga configurado. Si `OPENAI_BASE_URL` o `ANTHROPIC_BASE_URL` cambia ese destino, MADRE enseña solo el hostname y bloquea el turno antes de armar el briefing; un gateway verificado se autoriza por agente con `PULSE_ALLOW_CUSTOM_AGENT_ENDPOINTS`. Aplican la cuenta, los límites y los términos del destino efectivo. `@madre` y el archivista con Ollama no salen de la máquina.
|
|
201
201
|
- **Dos envíos propios, ambos bajo tu interruptor.** El sentinel, apagado por defecto, envía reportes redactados al colector del proyecto. El canal de liberación, encendido por defecto, pregunta a npm por la última versión: viaja el nombre del paquete, nada más, la misma petición que hace `npx`. `PULSE_UPDATE_CHECK=0` lo apaga.
|
|
202
202
|
- **Escritura.** En `#1` nadie escribe. En `#2` solo se añade: lo que existía se restaura al terminar el turno. En `#3` todo el proyecto salvo las zonas prohibidas, con checkpoint y `UNDO`. En un proyecto sin git, MADRE guarda sus fotografías en un repositorio sombra fuera del proyecto.
|
|
203
203
|
- **Memoria.** Todo lo dicho fuera de GHOST queda en `~/.pulse/rooms/<sala>/` y vuelve a los prompts de todos los agentes de esa sala. GHOST es la salida para lo que no debe recordarse; PRIVACY, para los nombres que nunca deben aparecer.
|
|
@@ -237,6 +237,7 @@ Cada módulo es un archivo. Los tuyos van en `~/.pulse/modules/` (todas las sala
|
|
|
237
237
|
| `PULSE_UPDATE_CHECK` | `1` | `0` apaga la consulta diaria a npm |
|
|
238
238
|
| `PULSE_REPORT_URL` · `PULSE_AUTO_REPORT` | colector del proyecto · `0` | Sentinel |
|
|
239
239
|
| `PULSE_AGENT_TIMEOUT_MS` · `PULSE_<AGENTE>_TIMEOUT_MS` | `180000` · — | Timeouts |
|
|
240
|
+
| `PULSE_ALLOW_CUSTOM_AGENT_ENDPOINTS` | — | Lista separada por comas (`codex`, `claude`) de gateways que el humano verificó y permite; sin ella, una dirección base no oficial se bloquea |
|
|
240
241
|
| `PULSE_MAX_MESSAGE_CHARS` | `20000` | Tamaño máximo de un mensaje |
|
|
241
242
|
| `PULSE_DELEGATION` · `PULSE_MAX_PLAN_STEPS` | `1` · `4` | Delegación |
|
|
242
243
|
| `PULSE_ESCALATION_MS` | `180000` | Cronómetro de la escalación |
|
|
@@ -246,4 +247,4 @@ Cada módulo es un archivo. Los tuyos van en `~/.pulse/modules/` (todas las sala
|
|
|
246
247
|
|
|
247
248
|
Cómo funcionan por dentro el ledger, el stream, la memoria, los adaptadores y la recuperación: [`docs/INTERNALS.md`](INTERNALS.md).
|
|
248
249
|
|
|
249
|
-
---
|
|
250
|
+
---
|
package/docs/SDK.md
CHANGED
|
@@ -26,7 +26,19 @@ export default {
|
|
|
26
26
|
};
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
**Tu módulo no importa nada.** Ni a MADRE, ni al SDK. Antes de instalarlo, MADRE copia tu archivo a una carpeta temporal aparte y lo carga ahí —así revisa qué es sin que corra donde vive— y en esa carpeta no hay `node_modules`: un `import` por nombre de paquete no resuelve y la instalación falla.
|
|
30
|
+
|
|
31
|
+
Si necesitas `defineModule` —para calcular algo antes de definir el módulo, por ejemplo— **MADRE te lo entrega**: exporta una función y lo recibes como argumento.
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
export default ({ defineModule }) => defineModule({
|
|
35
|
+
id: 'mi-modulo',
|
|
36
|
+
name: 'MI MÓDULO',
|
|
37
|
+
// …lo que hayas calculado arriba
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
> Los módulos que vienen con MADRE sí escriben `import { defineModule } from './sdk.mjs'`, porque viven dentro del paquete. **Si le pides a un agente que te escriba uno, va a leer esos archivos y copiar esa línea** — y no va a funcionar. Dile que exporte un objeto plano, o la función de arriba.
|
|
30
42
|
|
|
31
43
|
## Lo que un módulo puede declarar
|
|
32
44
|
|
|
@@ -38,7 +50,7 @@ Si prefieres importar el SDK, también vale: `import { defineModule } from '@jos
|
|
|
38
50
|
| `updatePlan(ctx, { latest })` | Cómo se trae esa versión nueva a esta computadora: `{ command, args, display, note, after }`. MADRE enseña `display` y no corre nada hasta que la humana lo leyó; la salida cae en la sala línea por línea. Sin comando, devuelve `{ command: null, note, download }` y la ficha manda a descargarlo |
|
|
39
51
|
| `updates` | `{ url }` https donde publicas **tu propio módulo**. Con eso su ficha trae un botón que va por el archivo, lo verifica igual que una instalación y lo reemplaza si pasa. Si lo instalaste desde un archivo, MADRE recuerda cuál y no necesitas declarar nada: edítalo y pide una copia nueva |
|
|
40
52
|
| `settings` | Valores por defecto. Viven en `~/.pulse/config.json` bajo `modules.<idEnCamelCase>`; `enabled` es el interruptor |
|
|
41
|
-
| `status(ctx)` | Qué muestra la tarjeta: `{ status: { installed, detail }, preflight: { ok, problems }, install: { display } }` |
|
|
53
|
+
| `status(ctx)` | Qué muestra la tarjeta: `{ status: { installed, detail }, preflight: { ok, problems }, install: { display } }`. `install` se devuelve desde aquí; no es un campo superior del módulo |
|
|
42
54
|
| `status(ctx)` → `runs` | Lo que tu módulo maneja y no es MADRE: `[{ name, version, target }]`. `version` es lo que encontraste en esta computadora (`null` si no está), `target` lo que instalarías. Si coincide con `tracks.name`, esa es la versión de la ficha |
|
|
43
55
|
| `toggle(ctx, payload)` | Sustituye el interruptor por defecto; `confirm: 'texto'` pide confirmación antes de encender |
|
|
44
56
|
| `onToggle(ctx, enabled)` | Reacciona al interruptor |
|
|
@@ -68,7 +80,7 @@ Todas las fichas de MODULES tienen los mismos pisos, en el mismo orden. No dibuj
|
|
|
68
80
|
| 4 · Ajustes | lo tuyo: selectores, interruptores, campos | `controls`, y lo que tu módulo lea de sí mismo |
|
|
69
81
|
| 5 · El interruptor | install, enable o disable. Solo, y siempre abajo | `toggle` / `installCommand` |
|
|
70
82
|
|
|
71
|
-
El estado es una palabra y un punto —`ON` u `OFF`—, nunca un botón:
|
|
83
|
+
El estado es una palabra y un punto —`ON` u `OFF`—, nunca un botón: las acciones se presionan abajo. La principal queda al final; un módulo `DEV` también ofrece `DESINSTALAR`, mientras que uno integrado solo puede apagarse. Un módulo apagado se atenúa entero menos esa fila.
|
|
72
84
|
|
|
73
85
|
## El `ctx`
|
|
74
86
|
|
|
@@ -95,11 +107,13 @@ ctx = {
|
|
|
95
107
|
|
|
96
108
|
## Que lo construya tu IA, en la sala
|
|
97
109
|
|
|
98
|
-
|
|
110
|
+
Escribe `/module <lo que debe hacer>` en el compositor. Es la entrada explícita al flujo guiado: si `#2 CREATE` no está activo, MADRE te pide elegirlo y conserva intacta tu petición. Con `#2` activo, mantiene el agente que elegiste y le entrega el contrato del SDK y el ejemplo. El agente debe escribir **un solo archivo** llamado `<id>.module.mjs` en su carpeta de borrador; no puede instalarlo por su cuenta.
|
|
111
|
+
|
|
112
|
+
MADRE reconoce ese nombre y pone una tarjeta en la sala: `INSTALL FOR EVERY ROOM` o `INSTALL FOR THIS PROJECT`. Léelo, decide, un clic. MADRE lo valida en una copia, lo guarda como `<id>.mjs` en la carpeta que elegiste y aparece en MODULES con la etiqueta `DEV`. También puedes pedir lo mismo en lenguaje natural mientras estés en `#2` o más; `/module` hace explícito el formato y no concede permisos por sí solo.
|
|
99
113
|
|
|
100
114
|
Los agentes nunca escriben en `~/.pulse/modules` ni en `.madre/modules`: proponen, tú instalas.
|
|
101
115
|
|
|
102
|
-
Un módulo tuyo se quita desde
|
|
116
|
+
Un módulo tuyo lleva `DEV` y se quita desde su propia tarjeta con `DESINSTALAR` —también aparece en la tarjeta `</>`—. MADRE confirma el archivo exacto, borra ese archivo y su registro de origen para actualizaciones, y lo retira de la sala sin reiniciarla. No borra archivos que el módulo haya creado ni sus ajustes guardados. Los módulos que vienen con MADRE no muestran ese botón: no se quitan, se apagan.
|
|
103
117
|
|
|
104
118
|
## Lo que un módulo no puede tocar
|
|
105
119
|
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
//
|
|
7
7
|
// The default export is a plain object: MADRE wraps it with defineModule.
|
|
8
8
|
// Everything is optional except id and name.
|
|
9
|
+
//
|
|
10
|
+
// A module imports nothing. MADRE loads your file from a scratch folder to check it before
|
|
11
|
+
// installing, and there is no node_modules there: `import … from '@jossuealcala/madre/sdk'`
|
|
12
|
+
// cannot resolve and the install fails. If you need defineModule, export a function and MADRE
|
|
13
|
+
// hands it to you: ({ defineModule }) => defineModule({ … }).
|
|
14
|
+
//
|
|
15
|
+
// MADRE's own modules do write `import { defineModule } from './sdk.mjs'` — they live inside the
|
|
16
|
+
// package. Copying that line from them is the one mistake that looks right and never works.
|
|
9
17
|
|
|
10
18
|
export default {
|
|
11
19
|
id: 'hello', // kebab-case; also the key under modules in ~/.pulse/config.json (camelCased)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jossuealcala/madre",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "MADRE · one local room where the AI coding agents already on your machine (Codex, Claude Code, Gemini CLI, OpenCode) work on a project together, with a shared memory every one of them recalls and a local @madre that speaks for it. Read-only by default, per-message permission modes up to a checkpointed CONTROL. Nothing leaves your machine on its own.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-agents",
|