chore: auto-commit (1 archivos)
- reports/ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
# Report 0001 — unibus: servicio LAN + Fase 2 (transporte sobre el bus)
|
||||
|
||||
- **Fecha:** 06/06/2026
|
||||
- **Autor:** Claude (Opus 4.8)
|
||||
- **Ámbito:** `projects/message_bus/apps/unibus` (sub-repo `dataforge/unibus`) y `~/DataProyects/Github/agents_and_robots` (Gitea `egutierrez/agents_and_robots`)
|
||||
- **Estado:** done (con un gap acotado documentado en Fase 2)
|
||||
|
||||
## Resumen
|
||||
|
||||
Dos entregas. **A)** `membershipd` pasa de proceso manual loopback a service systemd real
|
||||
alcanzable por la LAN: flag `--bind` que gobierna a la vez el HTTP de control y el NATS
|
||||
embebido, unit systemd-user con `Restart=always`, bloque `service:` completo y health
|
||||
verificado desde la IP de la LAN. Esto desbloquea que un teléfono/PC remoto conecte al bus.
|
||||
**B)** Fase 2 (branch-by-abstraction): se introduce una interfaz `Transport` + un tipo
|
||||
`InboundMessage` neutral (sin mautrix), se desacoplan las firmas del core de agentes, se
|
||||
implementa un `unibusTransport` sobre `pkg/client` de unibus, y un feature flag elige Matrix
|
||||
vs unibus por bot. Pre-requisito cumplido: el `Frame` de unibus gana threading/reply/reaction
|
||||
de forma aditiva.
|
||||
|
||||
Trabajo en ramas TBD: `issue/fase2-transport-service` (unibus) y `issue/unibus-transport`
|
||||
(agents_and_robots). Master nunca tocado directamente.
|
||||
|
||||
## Cambios
|
||||
|
||||
### A — Servicio (unibus)
|
||||
|
||||
| Cambio | Archivo | Por qué |
|
||||
|---|---|---|
|
||||
| Flag `--bind` (default 127.0.0.1) aplicado a HTTP + NATS | `cmd/membershipd/main.go` | Un solo flag gobierna la alcanzabilidad de ambos planos; `0.0.0.0` expone a la LAN |
|
||||
| `embeddednats.StartHost(storeDir, host, port)` (y `Start` backward-compatible) | `pkg/embeddednats/embeddednats.go` | Controlar la interfaz del NATS embebido sin romper playground/tests |
|
||||
| systemd-user unit `Restart=always` | `deploy/unibus-membershipd.service` | Service persistente; `on-failure` NO reinicia tras un SIGTERM limpio (exit 0) — el gotcha de `sqlite_api` |
|
||||
| `install.sh` idempotente + `README.md` | `deploy/` | build + symlink unit + enable/restart en un comando; operar/health documentado |
|
||||
| Bloque `service:` completo | `app.md` | `systemd-user`, `restart_policy: always`, health `/healthz`, `is_local_only: false`; valida `fn doctor services-spec` |
|
||||
|
||||
### Pre-Fase 2 — Frame threading (unibus, aditivo)
|
||||
|
||||
| Cambio | Archivo | Por qué |
|
||||
|---|---|---|
|
||||
| Campos `ThreadID`, `ReplyTo` (omitempty) + tipo `REACT` | `pkg/frame/frame.go` | Bots de chat necesitan replies, hilos y reacciones |
|
||||
| `PublishReply`, `React` (refactor de `Publish` a `publishFrame` compartido) | `pkg/client/client.go` | API de threading; la reacción viaja en el payload cifrado (confidencial en rooms E2E) |
|
||||
| Tests: round-trip threaded, back-compat de wire/firma, error path, reply+react E2E | `pkg/frame/*_test.go`, `pkg/client/client_test.go` | golden + edge + error path |
|
||||
|
||||
Aditivo de verdad: un frame sin threading serializa **sin** las claves `thr`/`re`, así que su
|
||||
wire y su firma Ed25519 son idénticos a antes → no rompe `Subscribe`/`Publish` ni los tests
|
||||
existentes.
|
||||
|
||||
### B — Fase 2 (agents_and_robots)
|
||||
|
||||
| Cambio | Archivo | Por qué |
|
||||
|---|---|---|
|
||||
| `Transport` interface + `InboundMessage` neutral + `Select`/`FlagEnabled` | `pkg/transport/` | Seam neutral (cero mautrix) entre el core del agente y el fabric de mensajería |
|
||||
| Flag `unibus-transport` (default off) | `dev/feature_flags.json` | Convivencia: con el flag ON cada bot opta in; el resto sigue en Matrix |
|
||||
| `handleEvent(…, *event.Event)` → `handleInbound(…, transport.InboundMessage)` (Agent + Robot) | `agents/handler.go`, `agents/robot.go` | Quitar `*event.Event` de las firmas; `inboundToMsgCtx` es el único punto de conversión a `decision.MessageContext` |
|
||||
| `RawMatrixClient() *mautrix.Client` → `Scanner() RoomScanner` | `agents/runtime.go`, `cmd/launcher/main.go` | Quitar el handle al cliente mautrix de la API pública; capability read-only |
|
||||
| Listener entrega `InboundMessage` (no el evento) | `shell/matrix/listener.go` | El evento mautrix ya no cruza la frontera al core |
|
||||
| `unibusTransport` + `DemoEchoHandler` (módulo Go anidado) | `shell/transportunibus/` | Implementa `Transport` sobre `pkg/client`; subjects `agent.<name>.{in,out}` |
|
||||
|
||||
## Verificación
|
||||
|
||||
### A — Servicio (evidencia ejecutable)
|
||||
|
||||
```
|
||||
$ systemctl --user is-active unibus-membershipd.service
|
||||
active
|
||||
|
||||
$ systemctl --user status unibus-membershipd.service # (extracto)
|
||||
Active: active (running)
|
||||
ExecStart=…/membershipd --bind 0.0.0.0
|
||||
|
||||
$ ss -tln | grep -E ':8470|:4250'
|
||||
LISTEN 0 4096 *:8470 *:* # HTTP control en todas las interfaces
|
||||
LISTEN 0 4096 *:4250 *:* # NATS data plane en todas las interfaces
|
||||
|
||||
$ curl -fsS -w " [HTTP %{http_code}]\n" http://127.0.0.1:8470/healthz
|
||||
{"status":"ok"} [HTTP 200]
|
||||
|
||||
$ curl -fsS -w " [HTTP %{http_code}]\n" http://192.168.1.129:8470/healthz # IP LAN
|
||||
{"status":"ok"} [HTTP 200]
|
||||
|
||||
$ ./fn doctor services-spec | grep unibus
|
||||
OK unibus systemd-user 8470 /healthz unibus-membershipd.service lucas-linux -
|
||||
```
|
||||
|
||||
**Cliente NATS conecta por IP LAN y publica/recibe** (worker→chat, ambos contra
|
||||
`nats://192.168.1.129:4250` + `http://192.168.1.129:8470`, vía el service systemd):
|
||||
|
||||
```
|
||||
[chat] subscribed to "proc.test.ticks"; waiting for messages
|
||||
[proc.test.ticks] DGCob41a: tick 1 @ 2026-06-06T16:07:16Z
|
||||
[proc.test.ticks] DGCob41a: tick 2 @ 2026-06-06T16:07:17Z
|
||||
[proc.test.ticks] DGCob41a: tick 3 @ 2026-06-06T16:07:18Z
|
||||
[proc.test.ticks] DGCob41a: tick 4 @ 2026-06-06T16:07:19Z
|
||||
```
|
||||
|
||||
### Pre-Fase 2 — Frame (unibus)
|
||||
|
||||
```
|
||||
$ cd projects/message_bus/apps/unibus
|
||||
$ CGO_ENABLED=0 go build ./... && CGO_ENABLED=0 go vet ./... # exit 0
|
||||
$ CGO_ENABLED=0 go test ./...
|
||||
ok github.com/enmanuel/unibus/pkg/frame
|
||||
ok github.com/enmanuel/unibus/pkg/client
|
||||
ok github.com/enmanuel/unibus/pkg/membership
|
||||
```
|
||||
|
||||
Tests nuevos: `TestThreadingRoundTrip` (golden), `TestNonThreadedWireBackCompat` (edge:
|
||||
asegura que `thr`/`re` no aparecen en frames no-threaded), `TestUnmarshalRejectsGarbage`
|
||||
(error path), `TestThreadedReplyAndReaction` (reply + reacción E2E en room cifrada ModeMatrix).
|
||||
|
||||
### B — Fase 2 (agents_and_robots)
|
||||
|
||||
Toda la compilación/test del repo `agents` se hace con `-tags goolm` (olm puro-Go) porque
|
||||
`libolm` (CGO) no está instalado en esta máquina; en producción se usa `libolm`.
|
||||
|
||||
```
|
||||
# Módulo anidado del transporte unibus (flag ON / camino unibus):
|
||||
$ cd shell/transportunibus && go test -tags goolm ./...
|
||||
ok github.com/enmanuel/agents/shell/transportunibus
|
||||
# TestDemoBotOverUnibus: un user-peer publica en agent.demo.in; el bot, movido por
|
||||
# unibusTransport, recibe un InboundMessage y responde "echo: hola bus" / "pong" en
|
||||
# agent.demo.out. TestRunStopsOnContextCancel: lifecycle (error path).
|
||||
|
||||
# Módulo principal (flag OFF / camino Matrix sigue vivo):
|
||||
$ go build -tags goolm ./... # exit 0
|
||||
$ go vet -tags goolm ./agents/ ./shell/matrix/ ./pkg/transport/ # exit 0
|
||||
$ go test -tags goolm ./pkg/transport/ ./agents/
|
||||
ok github.com/enmanuel/agents/pkg/transport # TestSelect (flag ON/OFF) + TestFlagEnabled
|
||||
ok github.com/enmanuel/agents/agents # core decoplado compila+pasa
|
||||
$ go test -tags goolm ./... # 26 paquetes ok, 1 FAIL pre-existente (ver gaps)
|
||||
```
|
||||
|
||||
## Gaps / pendientes
|
||||
|
||||
1. **Segundo host físico real para A.** El round-trip de A se probó desde esta misma máquina
|
||||
contra su IP de LAN `192.168.1.129` (no loopback), lo que ejercita el bind `0.0.0.0`. No
|
||||
había un segundo PC/teléfono disponible para conectar desde otra máquina; como los sockets
|
||||
escuchan en `*` (todas las interfaces, confirmado por `ss`), el comportamiento desde un host
|
||||
remoto es idéntico. El agente de frontend puede confirmarlo desde el móvil.
|
||||
|
||||
2. **El Agent "pesado" todavía no corre sobre unibus.** Conflicto real de dependencias entre
|
||||
ecosistemas: `unibus` depende de `fn-registry`, cuyo módulo raíz exige
|
||||
`maunium.net/go/mautrix v0.28.0`, incompatible con el `shell/matrix` del repo `agents`
|
||||
(escrito para `v0.21.1`; MVS forzaría v0.28 y rompe 3 call-sites de la API mautrix). Por eso
|
||||
el `unibusTransport` vive en un **módulo Go anidado** (`shell/transportunibus/go.mod`): tira
|
||||
de la mautrix nueva transitivamente sin recompilar el código Matrix del padre, y ambos
|
||||
ecosistemas conviven. Consecuencia: el core del Agent (módulo principal) no puede instanciar
|
||||
`unibusTransport` directamente sin reintroducir el conflicto. El seam neutral
|
||||
(`pkg/transport.Transport`) sí está y el bot demo corre sobre el bus; **migrar el Agent
|
||||
completo a unibus requiere primero subir `agents` a mautrix v0.28** (migración aparte, fuera
|
||||
de alcance de esta tanda).
|
||||
|
||||
3. **`Scanner() RoomScanner` sigue devolviendo tipos mautrix** en sus métodos
|
||||
(`*mautrix.RespJoinedRooms`, …). El escaneo de rooms es una capability intrínsecamente de
|
||||
Matrix (enumera rooms de Matrix); la neutralización quita el handle al cliente crudo y lo
|
||||
sustituye por una interfaz read-only mínima, pero no puede ser 100% neutral porque el unibus
|
||||
no participa en ese escaneo.
|
||||
|
||||
4. **`libolm` no instalado** en esta máquina → el repo `agents` se compila/test con
|
||||
`-tags goolm` (olm puro-Go). En producción se usa `libolm`. Pendiente: instalar
|
||||
`libolm-dev` si se quiere validar el path libolm localmente.
|
||||
|
||||
5. **Test pre-existente en rojo, ajeno a este trabajo:** `shell/logger/TestCleanOldLogs` falla
|
||||
porque es dependiente de fecha (espera logs de marzo 2026 que la limpieza por antigüedad
|
||||
borra estando hoy a 06/06/2026). Ya fallaba en el baseline antes de tocar nada; no toqué
|
||||
`shell/logger`. Issue separado recomendado.
|
||||
|
||||
6. **Sin push a remoto.** El trabajo queda consolidado en `master` local de ambos repos; el
|
||||
push a Gitea queda para `/full-git-push` del operador.
|
||||
- `agents_and_robots`: `master` = merge `--no-ff` de `issue/unibus-transport` (commit
|
||||
`aa595f5`), build + tests verdes; rama borrada.
|
||||
- `unibus`: **condición de carrera entre dos agentes en el mismo repo.** El agente de
|
||||
frontend trabajaba en paralelo en el mismo working tree de unibus y nuestros `git checkout`
|
||||
se interleavearon: mis commits A1/A2/A3 (`--bind`, deploy, service block) acabaron
|
||||
directamente en `master`, y los de threading + bump (`2209283`, `69079d1`) cayeron sobre la
|
||||
rama del otro agente `issue/frontend-web-movil`. Como esos dos commits son lineales sobre
|
||||
mi A3 (`b2e6712`) y NO contenían trabajo del otro agente, consolidé `master` con
|
||||
`git merge --ff-only 69079d1` (sin duplicar commits, sin tocar la rama del frontend). Estado
|
||||
final: `master` de unibus contiene TODO mi trabajo (A1-A3 + threading + bump 0.3.0), tests
|
||||
`pkg/...` verdes, working tree con el WIP del frontend (mobile/playground/android/web)
|
||||
intacto. **Lección operativa:** dos agentes Claude en el mismo repo comparten HEAD/índice;
|
||||
hace falta worktrees separados (`git worktree`) o repos clonados por agente para evitarlo.
|
||||
@@ -0,0 +1,125 @@
|
||||
# Report 0002 — Cliente humano de unibus: SPA web + app Android nativa
|
||||
|
||||
- **Fecha:** 06/06/2026
|
||||
- **Autor:** agente (Claude) — frente frontend
|
||||
- **Ámbito:** `mobile/unibus.go`, `playground/server.go` (gateway), `web/` (SPA nueva), `android/` (app nueva)
|
||||
- **Estado:** done (con gaps documentados)
|
||||
|
||||
## Resumen
|
||||
|
||||
Se construyó el cliente humano de unibus en dos frentes, ambos verificados end-to-end:
|
||||
|
||||
1. **Web**: SPA de chat (React + Vite + TypeScript + Mantine v9) contra el gateway Go
|
||||
(`playground/server.go`), que monta un peer real por identidad y emite los mensajes
|
||||
recibidos por SSE. Dos identidades crean una room cifrada E2E, una invita a la otra y
|
||||
ambas chatean en vivo.
|
||||
2. **Móvil**: app Android nativa (Kotlin + Jetpack Compose) sobre el binding gomobile
|
||||
`mobile/unibus.go`. El cifrado y el transporte NATS corren **en el dispositivo** (cada
|
||||
teléfono es un peer real del bus). La app genera identidad, conecta al `membershipd`,
|
||||
crea/se une a una room y envía/recibe mensajes por el bus.
|
||||
|
||||
No se reimplementó protocolo ni criptografía en JS ni en Kotlin: todo delega en `pkg/client`
|
||||
a través del binding y del gateway.
|
||||
|
||||
## Cambios
|
||||
|
||||
| Archivo / dir | Qué | Por qué |
|
||||
|---|---|---|
|
||||
| `mobile/unibus.go` | Añadidos `Card()` (exporta la identidad pública del peer como JSON portable), `Invite(roomID, peerCard)` y `Kick(roomID, endpointID)` al binding. | La UI móvil necesita invitar/expulsar; `Card()` permite el intercambio de identidad peer-a-peer (paste/QR) sin un gateway. |
|
||||
| `playground/server.go` | Añadidos `GET /api/rooms?peer=` (rooms del peer), `GET /api/members?room_id=` (proxy al control plane) y middleware `withCORS` (preflight + headers) para el dev server de Vite. | La SPA necesita listar rooms/miembros y llamar al gateway desde otro origen. |
|
||||
| `web/` | SPA nueva: conexión (gateway URL + identidad), navbar (crear/unir/listar rooms), panel central (mensajes en vivo por SSE + composer), panel lateral (miembros, invitar por peer, expulsar si owner). Mantine v9, `@tabler/icons-react`, sin Tailwind ni CSS manual. | Cliente web de chat. |
|
||||
| `android/` | App Kotlin + Compose: pantalla de conexión (Host + NATS + identidad), `BusViewModel` que dirige el binding (llamadas de red en `Dispatchers.IO`, frames entrantes vía `StateFlow`), pantalla de chat (crear/unir room, enviar, recibir). `.aar` generado con `gomobile bind`. | Cliente móvil con E2E en el dispositivo. |
|
||||
|
||||
## Verificación (evidencia ejecutable)
|
||||
|
||||
### Builds
|
||||
|
||||
```
|
||||
# Binding + gateway (Go, sin CGO)
|
||||
$ CGO_ENABLED=0 go build ./mobile/ ./playground/ → OK
|
||||
$ CGO_ENABLED=0 go vet ./mobile/ ./playground/ → OK
|
||||
|
||||
# .aar gomobile
|
||||
$ gomobile bind -target=android -androidapi 21 -javapkg com.unibus.core \
|
||||
-o android/app/libs/unibus.aar ./mobile → unibus.aar (24 MB)
|
||||
|
||||
# SPA
|
||||
$ cd web && pnpm build
|
||||
✓ tsc -b && vite build → 6949 modules transformed, dist/ generado (exit 0)
|
||||
|
||||
# APK
|
||||
$ cd android && ./gradlew assembleDebug
|
||||
BUILD SUCCESSFUL in 1m13s
|
||||
→ app/build/outputs/apk/debug/app-debug.apk (41 MB, incluye libgojni.so)
|
||||
```
|
||||
|
||||
### Web — flujo cifrado E2E por curl (determinista)
|
||||
|
||||
Gateway (`go run ./playground`, web :7700) + dos peers `ana`/`leo`:
|
||||
|
||||
```
|
||||
1. POST /api/peer ana → endpoint cc9f8Gm_RQZ76lX2I6C0ZepmiC82AG19M_ajiaDW4P8
|
||||
POST /api/peer leo → endpoint yT7GWz97tRcCx3i4st43d4LcGC85Tl4PDZenycUaQ_I
|
||||
2. POST /api/room {peer:ana, encrypt:true} → room_id 01KTEVZEE7B7AQAD72WSDHH3Y3
|
||||
3. POST /api/invite {peer:ana, target:leo} → {"status":"invited"}
|
||||
4. POST /api/join {peer:leo} → {"encrypt":true,"subject":"room.general"}
|
||||
5. SSE leo + POST /api/publish ana "hola leo, mensaje cifrado E2E":
|
||||
leo recibe → data: {"sender":"cc9f8...","text":"hola leo, mensaje cifrado E2E","encrypted":true}
|
||||
6. GET /api/rooms?peer=ana → [{"encrypt":true,"room_id":"01KTEVZEE...","subject":"room.general"}]
|
||||
7. GET /api/members?room_id → [{ana,role:owner}, {leo,role:member}]
|
||||
```
|
||||
|
||||
### Web — UI con dos pestañas (SPA real, `pnpm preview` :4173)
|
||||
|
||||
- Pestaña ana y pestaña leo conectadas como identidades distintas a la room cifrada.
|
||||
- leo → ana: "hola ana, soy leo desde la SPA" (18:31:05) llegó **en vivo por SSE** a ana.
|
||||
- ana → leo: "¡recibido leo! ana responde cifrado" (18:32:28), bucle bidireccional.
|
||||
- Panel de miembros: ana **OWNER** + leo; botón expulsar visible solo para el owner;
|
||||
selector de invitación con los peers conectados.
|
||||
|
||||
### Móvil — AVD Pixel_API34 (emulador headless)
|
||||
|
||||
```
|
||||
$ adb install -r app-debug.apk → Success
|
||||
$ adb shell am start -n com.unibus.app/.MainActivity
|
||||
→ proceso vivo, sin FATAL/AndroidRuntime, libgojni.so cargada (lib/x86_64)
|
||||
→ pantalla "Conectado como android" (newSession contra membershipd 10.0.2.2:8470/4250)
|
||||
→ "Room creada · 01KTEWTZ8TB1NYEGNNR7F3MY5G"
|
||||
→ enviado "peer movil real en el bus" → burbuja recibida con hora 18:39:35
|
||||
```
|
||||
|
||||
La burbuja con timestamp prueba el camino completo **en el dispositivo**: el código no añade
|
||||
el mensaje localmente al publicar; solo lo pinta `FrameListener.onFrame`, así que su aparición
|
||||
demuestra que el frame viajó por NATS y volvió al peer. Captura en
|
||||
`reports/assets/unibus_movil.png`.
|
||||
|
||||
## Gaps / pendientes
|
||||
|
||||
- **Media (PublishMedia/FetchMedia)**: el `pkg/client` ya la soporta, pero NO está expuesta en
|
||||
el binding móvil ni en la SPA. Requiere que `FrameListener.onFrame` señale frames con `Blob`
|
||||
(hoy entrega solo texto). Pendiente para v2.
|
||||
- **ListMembers en el binding móvil**: necesita un método **público** en `pkg/client`
|
||||
(`ListMembers(roomID)`); hoy es privado (`signerPub` lo usa internamente). **Dependencia del
|
||||
CORE**, no implementada aquí por contrato (no tocar `pkg/`). La SPA sí lista miembros porque
|
||||
el gateway hace proxy al control plane.
|
||||
- **Threading/reply/reaction**: el otro agente añadió `ThreadID`/`ReplyTo`/tipo `REACT` al
|
||||
`Frame` (ya en master). Ni la SPA ni la app móvil los usan todavía — gap de UI, no de backend.
|
||||
- **Invite/Kick en la UI móvil**: el binding ya expone `Card()`/`Invite()`/`Kick()`, pero la
|
||||
app Kotlin v1 no tiene aún la pantalla de intercambio de "card" (paste/QR) ni el botón de
|
||||
expulsar. El chat cleartext y E2E self-echo funcionan; el flujo de dos teléfonos invitándose
|
||||
queda para v2.
|
||||
- **Auth del gateway web**: el gateway identifica peers por nombre (modelo del playground), sin
|
||||
autenticación de sesión. Suficiente para uso local/LAN; endurecer es fase posterior (igual que
|
||||
el control plane, que en v1 no tiene TLS ni auth en GETs).
|
||||
- **`.aar` no versionado**: es un artefacto de ~24 MB; se regenera con `gomobile bind` (ver
|
||||
`android/README.md`). Gitignored.
|
||||
|
||||
## Notas
|
||||
|
||||
- Web: `cd web && pnpm install && pnpm dev` (o `pnpm preview` tras `pnpm build`); conectar a
|
||||
`http://localhost:7700` (gateway: `go run ./playground`).
|
||||
- Móvil: ver `android/README.md` para generar el `.aar`, compilar el APK y probar en el AVD.
|
||||
Desde el emulador, el host del PC es `10.0.2.2`; desde un teléfono físico, la IP LAN del PC.
|
||||
- Mantine v9 exige **React 19** (peerDependency `^19.2.0`); con React 18 la SPA compila pero
|
||||
no monta en runtime (`s is not a function`). Fijado React 19 en `web/package.json`.
|
||||
- pnpm 10 bloquea los build scripts: `web/pnpm-workspace.yaml` con `allowBuilds: { esbuild: true }`.
|
||||
@@ -0,0 +1,87 @@
|
||||
# Report 0002 — Matrix-out: los bots de agents_and_robots hablan solo por unibus
|
||||
|
||||
- **Fecha:** 07/06/2026
|
||||
- **Autor:** Claude (Opus 4.8)
|
||||
- **Ámbito:** `~/DataProyects/Github/agents_and_robots` (Gitea `egutierrez/agents_and_robots`) y `projects/message_bus/apps/unibus` (`dataforge/unibus`)
|
||||
- **Estado:** done (con gaps acotados)
|
||||
|
||||
## Resumen
|
||||
|
||||
Se arranca **todo Matrix (mautrix) de raíz** de `agents_and_robots` y se reconecta el core de
|
||||
agentes al bus `unibus` por el seam neutral `pkg/transport`. Modelo **"todo son rooms"** con **E2E**
|
||||
(`room.ModeMatrix`): el bot descubre por polling las rooms a las que lo invitan, se une, descifra y
|
||||
**responde en la misma room**. Resultado: el binario de agentes no contiene una sola línea de
|
||||
mautrix y compila sin `-tags goolm` ni `libolm`. Trabajo en ramas TBD, mergeado a master en ambos
|
||||
repos.
|
||||
|
||||
Antecedente: la migración a mautrix v0.28 que se barajó NO era necesaria — era un parche para
|
||||
convivencia. Al quitar Matrix, el conflicto de versiones desaparece de raíz.
|
||||
|
||||
## Cambios
|
||||
|
||||
### unibus — descubrimiento de rooms (v0.4.0)
|
||||
| Cambio | Archivo |
|
||||
|---|---|
|
||||
| `GET /members/{endpoint}/rooms` (rooms de un endpoint + rol) | `pkg/membership/server.go` |
|
||||
| `Store.ListRoomsForEndpoint` (JOIN members+rooms) | `pkg/membership/store.go` |
|
||||
| `client.ListMyRooms() []RoomRef` | `pkg/client/client.go` |
|
||||
| Tests store + e2e cliente (descubrir room por invitación) | `pkg/membership/store_test.go`, `pkg/client/client_test.go` |
|
||||
|
||||
El control plane es pull (no hay push de invitaciones); un peer recién invitado a una room cifrada
|
||||
la descubre por polling y luego hace `Join` + `Subscribe`.
|
||||
|
||||
### agents_and_robots — Matrix-out (4 commits)
|
||||
| Cambio | Detalle |
|
||||
|---|---|
|
||||
| **Borrado** | `shell/matrix/`, `tools/matrix/`, `cmd/verify/`, `cmd/agentctl/avatar.go`, dep `maunium.net/go/mautrix` |
|
||||
| **Config** | `MatrixCfg` → `BusCfg{NatsURL,CtrlURL,IdentityPath,Handle,CommandPrefix,Threads}`; loader valida `bus.*` |
|
||||
| **Interfaces** | `MatrixSender` → `Sender` en `effects/runner.go`, `cron/scheduler.go`, tool |
|
||||
| **Orquestador** | `shell/orchestration` despojado de mautrix (RoomScanner/scan/membership); aparcado, no se wirea |
|
||||
| **Transporte** | `shell/transportunibus` pasa al módulo principal (borrado el `go.mod` anidado) y se reescribe **room-based**: descubre (`ListMyRooms` polling) → `Join`+`Subscribe`; ignora sus propios frames; `IsDirectMsg` = room de 2 miembros (GET `/rooms/{id}/members` cacheado); `IsMention` = handle en el body; `Reply` publica en la room de origen. `busSender` adapta runner/cron/tools |
|
||||
| **Reconexión** | `runtime.go`/`robot.go` construyen el transport desde `BusCfg`, inyectan `busSender`, `Run` arranca `transport.Run(handleInbound)`; `sendReply` por el bus; nueva tool `bus_send` |
|
||||
| **Bots** | bloque `matrix:` → `bus:` en los 5 `agents/*/config.yaml` |
|
||||
|
||||
## Verificación
|
||||
|
||||
```
|
||||
# unibus (Fase 0)
|
||||
$ cd projects/message_bus/apps/unibus && CGO_ENABLED=0 go test ./...
|
||||
ok pkg/membership ok pkg/client ok pkg/frame
|
||||
$ ./fn doctor services-spec | grep unibus # service vivo en la LAN
|
||||
OK unibus systemd-user 8470 /healthz ...
|
||||
$ curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8470/members/x/rooms
|
||||
200
|
||||
|
||||
# agents (Matrix-out), en master
|
||||
$ go build ./... # exit 0, SIN -tags goolm
|
||||
$ go vet ./... # exit 0
|
||||
$ grep -rl maunium --include='*.go' . | grep -v /vendor/ # vacío
|
||||
$ grep -c maunium go.mod # 0
|
||||
$ go test -count=1 ./shell/transportunibus/ ./pkg/transport/
|
||||
ok shell/transportunibus (2.3s) ok pkg/transport
|
||||
```
|
||||
|
||||
Test e2e clave `TestBotEchoesInEncryptedRoom` (room-based, E2E): un peer "humano" crea room
|
||||
`ModeMatrix`, invita al bot por su endpoint, publica; el bot descubre/join/subscribe, descifra y
|
||||
**responde en la misma room** con ancla `ReplyTo`; el humano recibe la respuesta descifrada.
|
||||
|
||||
Suite completa agents: **25 paquetes ok**, 1 FAIL pre-existente ajeno (`shell/logger/TestCleanOldLogs`,
|
||||
dependiente de fecha contra fixtures de marzo).
|
||||
|
||||
## Gaps / pendientes
|
||||
|
||||
1. **Directorio de bots.** El frontend/humano necesita el `EndpointID` (+ claves públicas) del bot
|
||||
para invitarlo a una room cifrada. Hoy el bot lo expone (`Transport.BusEndpoint()`) y se invita
|
||||
a mano; un directorio formal (registro en membershipd o subject de anuncio) es fase posterior.
|
||||
2. **member-count cacheado sin invalidación**: `IsDirectMsg` se fija la primera vez por room. Si una
|
||||
room de 2 crece, sigue marcada como DM. Aceptable v1 (la mención sigue disparando el LLM).
|
||||
3. **Orquestador multi-bot aparcado**: el paquete compila sin mautrix pero no se wirea. Su
|
||||
reimplementación sobre `GET /rooms/{id}/members` + `bus.AgentMessage` queda para después.
|
||||
4. **Presencia / typing / avatars**: no existen en unibus v1 → eliminados (no-op). Features nuevas
|
||||
del bus si se quieren.
|
||||
5. **Smoke de launcher real**: el e2e valida transport + handler de eco; un arranque del `launcher`
|
||||
con un Agent real (LLM) contra el service vivo no se ha probado (requiere config LLM + invitar al
|
||||
bot). Recomendado como siguiente validación manual.
|
||||
6. **Sin push**: master local en ambos repos; push vía `/full-git-push` del operador.
|
||||
7. **Carrera git en unibus**: el agente de frontend trabaja en paralelo en el mismo repo unibus;
|
||||
Fase 0 se consolidó vigilando HEAD. agents_and_robots es repo aparte (sin carrera).
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 685 KiB |
Reference in New Issue
Block a user