agents-city 0.4.0 → 0.5.1

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.
Files changed (96) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.es.md +240 -11
  3. package/README.md +238 -11
  4. package/benchmarks/latency/fake-claude-cli.mjs +2 -1
  5. package/benchmarks/reception/README.md +16 -0
  6. package/benchmarks/reception/run.mjs +84 -0
  7. package/bin/agents-city.js +1 -0
  8. package/bin/connect +5 -0
  9. package/bin/hall.html +117 -18
  10. package/bin/navegador.mjs +62 -1
  11. package/bin/serve.py +106 -97
  12. package/bin/test +14 -3
  13. package/bin/test-arnes.py +192 -0
  14. package/bin/test-cage.py +9 -0
  15. package/bin/test-channel.py +178 -12
  16. package/bin/test-claude-runtime.py +132 -4
  17. package/bin/test-connect-client.mjs +609 -0
  18. package/bin/test-connect.py +52 -0
  19. package/bin/test-contracts.py +214 -0
  20. package/bin/test-doctor.py +31 -0
  21. package/bin/test-i18n.py +189 -0
  22. package/bin/test-navegador.py +52 -0
  23. package/bin/test-seat.py +78 -20
  24. package/bin/test-security.py +2 -0
  25. package/bin/test-serve.py +292 -0
  26. package/city/web/dist-hall/hall.js +733 -209
  27. package/city/web/src/bienvenida.ts +14 -13
  28. package/city/web/src/casa.ts +98 -56
  29. package/city/web/src/demo.ts +61 -31
  30. package/city/web/src/es.ts +187 -27
  31. package/city/web/src/explorador.ts +70 -43
  32. package/city/web/src/hall.ts +468 -98
  33. package/city/web/src/idioma.ts +21 -1
  34. package/city/web/src/motores.ts +48 -1
  35. package/city/web/src/vista.ts +58 -0
  36. package/demo/graba.py +5 -10
  37. package/docs/managed-connect.md +237 -0
  38. package/docs/security.md +41 -2
  39. package/package.json +8 -4
  40. package/plugin/.claude-plugin/plugin.json +1 -1
  41. package/plugin/channel/adapter-prompts.ts +10 -0
  42. package/plugin/channel/adapter.js +78 -31
  43. package/plugin/channel/agents_city_hybrid_crypto_bg.wasm +0 -0
  44. package/plugin/channel/bus.js +109 -65
  45. package/plugin/channel/bus.ts +11 -1
  46. package/plugin/channel/client.js +67 -30
  47. package/plugin/channel/delivery-queue.ts +160 -21
  48. package/plugin/channel/hub/remote-roads.ts +32 -1
  49. package/plugin/channel/hub/road-controller.ts +116 -16
  50. package/plugin/channel/kinsh_vodozemac_wasm_bg.wasm +0 -0
  51. package/plugin/channel/licenses/CONNECT_CLIENT_THIRD_PARTY_NOTICES.md +21 -0
  52. package/plugin/channel/licenses/HYBRID_CRYPTO_THIRD_PARTY_NOTICES.md +12 -0
  53. package/plugin/channel/licenses/hybrid-crypto-Apache-2.0.txt +201 -0
  54. package/plugin/channel/licenses/keyring-MIT.txt +21 -0
  55. package/plugin/channel/licenses/vodozemac-Apache-2.0.txt +201 -0
  56. package/plugin/channel/local-hub.js +2080 -97
  57. package/plugin/channel/local-hub.ts +5 -4
  58. package/plugin/channel/managed-connect/bridge.ts +208 -0
  59. package/plugin/channel/managed-connect/cli.ts +416 -0
  60. package/plugin/channel/managed-connect/device.ts +15 -0
  61. package/plugin/channel/managed-connect/local-cities.ts +94 -0
  62. package/plugin/channel/managed-connect/person-message.ts +74 -0
  63. package/plugin/channel/managed-connect/reception-bridge.ts +341 -0
  64. package/plugin/channel/managed-connect/relay-session.ts +7 -0
  65. package/plugin/channel/managed-connect/storage.ts +653 -0
  66. package/plugin/channel/managed-connect/transport.ts +87 -0
  67. package/plugin/channel/managed-connect-cli.js +4777 -0
  68. package/plugin/channel/managed-connect-cli.ts +7 -0
  69. package/plugin/channel/managed-connect-client.d.ts +261 -0
  70. package/plugin/channel/managed-connect-client.js +6572 -0
  71. package/plugin/channel/managed-connect-client.manifest.json +38 -0
  72. package/plugin/channel/package-lock.json +123 -105
  73. package/plugin/channel/package.json +4 -4
  74. package/plugin/channel/protocol.ts +4 -0
  75. package/plugin/channel/reception.ts +896 -0
  76. package/plugin/channel/road-cli.ts +1 -1
  77. package/plugin/channel/runtime/arnes.json +189 -0
  78. package/plugin/channel/runtime/arnes.ts +47 -0
  79. package/plugin/channel/runtime/claude.ts +18 -0
  80. package/plugin/channel/runtime/codex-config.ts +51 -1
  81. package/plugin/channel/runtime/codex.ts +31 -11
  82. package/plugin/channel/runtime/kimi.ts +6 -4
  83. package/plugin/channel/runtime-files.ts +39 -5
  84. package/plugin/channel/runtime-gateway.js +426 -100
  85. package/plugin/channel/runtime-gateway.ts +12 -0
  86. package/plugin/channel/trust/agents-city-sandbox-roots.json +66 -0
  87. package/plugin/scripts/arnes.py +271 -0
  88. package/plugin/scripts/busca.py +46 -10
  89. package/plugin/scripts/cage.py +5 -0
  90. package/plugin/scripts/city-session.sh +144 -28
  91. package/plugin/scripts/crecimiento.py +4 -1
  92. package/plugin/scripts/demos.py +58 -43
  93. package/plugin/scripts/desinstala.py +36 -21
  94. package/plugin/scripts/doctor.py +37 -15
  95. package/plugin/scripts/read-card.py +48 -8
  96. package/plugin/scripts/reception.py +665 -0
@@ -9,7 +9,7 @@
9
9
  {
10
10
  "name": "city",
11
11
  "source": "./plugin",
12
- "version": "0.4.0",
12
+ "version": "0.5.1",
13
13
  "description": "Operate one autonomous city seat, its domain, role, repo support agents, goal, recognised skills and explicit roads to other cities."
14
14
  }
15
15
  ]
package/README.es.md CHANGED
@@ -106,7 +106,7 @@ Esto es `0.x` a propósito: los comandos ya se usan hoy, pero los formatos de
106
106
  fichero y las APIs todavía pueden cambiar entre versiones menores. Aquí nada
107
107
  pretende estar congelado.
108
108
 
109
- Necesitas Node.js 22 o superior, Python 3 y tmux; los detalles están en la
109
+ Necesitas Node.js 22.13 o superior, Python 3 y tmux; los detalles están en la
110
110
  [tabla de requisitos](#requisitos-base), y `agents-city seat` se ofrece a
111
111
  instalar tmux si falta. No se instala nada en el sistema más allá de la carpeta
112
112
  global de npm de tu Node activo.
@@ -162,7 +162,7 @@ agents-city --version
162
162
 
163
163
  | Requisito | Para qué se usa |
164
164
  |---|---|
165
- | Node.js 22 o posterior | paquete npm, bus WebSocket y frontends |
165
+ | Node.js 22.13 o posterior | paquete npm, bus WebSocket, recepción local y frontends |
166
166
  | npm | instalación y empaquetado |
167
167
  | Python 3 | Hall, onboarding, ciudades, mapas y utilidades |
168
168
  | bash | sesiones y launchers |
@@ -491,6 +491,7 @@ agents-city setup
491
491
  agents-city seat
492
492
  agents-city cities
493
493
  agents-city road
494
+ agents-city connect
494
495
  agents-city bus
495
496
  agents-city committee
496
497
  agents-city agents
@@ -583,6 +584,19 @@ traducciones se indexan por la frase en inglés, así que lo que aún no esté
583
584
  traducido cae en un inglés legible y no en un identificador — un texto nuevo
584
585
  nunca se queda bloqueado esperando a la traducción.
585
586
 
587
+ La cobertura es una prueba, no una costumbre. `bin/test-i18n.py` lee las rutas de
588
+ renderizado, saca cada frase en inglés que una persona va a ver, y falla cuando
589
+ alguna no tiene español — así una vista nueva no puede colarse sin traducir, que
590
+ es como la cobertura había caído al 40% sin que nadie se diera cuenta.
591
+
592
+ El mecanismo evidente — barrer el DOM renderizado y traducir lo que coincida con
593
+ una clave — es a propósito lo que **no** se hace. En el DOM no hay forma de
594
+ distinguir una frase que escribió este producto de un nombre de ciudad o de
595
+ agente que escribió alguien, así que a quien tenga una ciudad llamada `Overview`
596
+ se le renombraría sola. Esa distinción sólo existe en el fuente, entre un literal
597
+ y una interpolación, y ahí es donde se comprueba: lo que lleve un `${}` se
598
+ salta.
599
+
586
600
  ### `agents-city setup`
587
601
 
588
602
  Crea o selecciona una ciudad y abre el Hall; con `--tui` entrega el flujo a
@@ -689,6 +703,59 @@ agents-city road disconnect producto <city-id-remoto>
689
703
  No se puede conectar una ciudad consigo misma. Una invitación remota debe
690
704
  aceptarse de forma independiente en cada máquina.
691
705
 
706
+ ### `agents-city connect`
707
+
708
+ Empareja este ordenador con un servicio de Roads gestionadas. No crea una
709
+ conexión unilateral: las dos personas la aprueban en el servicio y quien la
710
+ recibe ve a la otra persona en su recepción humana privada, sin exponer un
711
+ catálogo de ciudades. El cliente público implementa el protocolo v4; el servicio
712
+ alojado queda fuera de este repositorio y todavía no está habilitado en
713
+ producción ni auditado de forma independiente.
714
+
715
+ ```bash
716
+ agents-city connect --service https://connect.example.com --trust-file roots.json
717
+ agents-city connect --city producto
718
+ agents-city connect --all
719
+ agents-city connect status
720
+ agents-city connect roads
721
+ ```
722
+
723
+ El comando genera material Ed25519/X25519, Olm y ML-KEM-768 firmado en este
724
+ ordenador, muestra un PASCO de un solo uso y abre el navegador para autorizarlo.
725
+ Sólo sube material público. Las claves privadas, el estado del ratchet, las
726
+ semillas ML-KEM y los reintentos quedan cifrados en
727
+ `~/.agents-city/.runtime/connect/vault/`; la clave de envoltura permanece en el
728
+ Llavero de macOS, Credential Manager de Windows o Secret Service de Linux. El
729
+ cliente falla de forma cerrada si el keyring no está disponible. La jaula sella
730
+ el vault para las ventanas de agentes de repositorio en macOS y Linux.
731
+
732
+ La cadena de raíces firmada indicada con `--trust-file` es obligatoria en el
733
+ primer emparejamiento con un servicio que no sea de desarrollo. El cliente
734
+ conserva la última versión aceptada. Una raíz posterior debe continuar desde
735
+ esa raíz local exacta y llevar suficientes firmas de las autoridades offline
736
+ anteriores y nuevas; se rechazan saltos, rollback, caducidad y cambios
737
+ silenciosos de operador o testigo. El protocolo v4 verifica después al peer
738
+ mediante transparencia de claves, protege el primer mensaje Olm con X25519 +
739
+ ML-KEM-768 híbrido y usa el Double Ratchet de Olm. Los envíos sellados normales
740
+ omiten remitente, dispositivo, ciudad y Road de la petición exterior. Esto no
741
+ oculta a Cloudflare la IP, el momento o el tamaño rellenado; los pasos
742
+ posteriores del ratchet son clásicos.
743
+
744
+ El paquete incluye una raíz pública únicamente para el origen exacto del
745
+ sandbox gestionado. Los servicios autoalojados siguen necesitando su
746
+ `--trust-file` revisado. Una raíz devuelta por el propio servicio nunca se
747
+ acepta como primer anclaje.
748
+
749
+ `--city` elige un hub local que mantiene viva la recepción del propietario; no
750
+ elige un destinatario ni se revela a la otra persona. Un solo hub por ordenador
751
+ mantiene el lease y una conexión cifrada saliente; no se abre ningún puerto
752
+ público. Usa `--service URL` o `AGENTS_CITY_CONNECT_URL` para un endpoint piloto.
753
+ El servidor alojado no forma parte de este repositorio Apache; el cliente y el
754
+ protocolo auditables sí.
755
+
756
+ [docs/managed-connect.md](docs/managed-connect.md) detalla el contrato de claves,
757
+ sobres, cifrado, ACK, revocación y modelo de amenazas.
758
+
692
759
  ### `agents-city bus`
693
760
 
694
761
  Opera mensajes entre asientos sobre carreteras ya declaradas.
@@ -703,7 +770,7 @@ agents-city bus send '*' "Aviso para todas mis carreteras"
703
770
  | Subcomando | Efecto |
704
771
  |---|---|
705
772
  | `roster` | devuelve carreteras y presencia online conocida |
706
- | `inbox` | devuelve y consume el inbox pendiente; el historial append-only permanece |
773
+ | `inbox` | devuelve y consume el siguiente lote aprobado de hasta 20; el texto gestionado no aparece hasta que el propietario lo envía desde el ayuntamiento |
707
774
  | `send owner/city TEXTO` | envía a un destino permitido |
708
775
  | `send '*' TEXTO` | envía a todas las carreteras; exige al menos una |
709
776
 
@@ -1572,9 +1639,28 @@ AGENTS_CITY_DATA="$HOME/.agents-city/$CITY_OWNER/producto" \
1572
1639
  En una sesión normal no hace falta establecer `AGENTS_CITY_DATA`: ya está
1573
1640
  inyectado en cada ventana. El ejemplo lo hace explícito para una terminal externa.
1574
1641
 
1575
- ### Caso 10: conectar ciudades de dos máquinas o personas
1642
+ ### Caso 10: conectar dos personas desde ordenadores distintos
1576
1643
 
1577
- En la máquina A:
1644
+ Con un operador de Roads gestionadas, cada persona empareja su ordenador.
1645
+ `--city` elige el hub local que inicia la recepción del propietario; no revela
1646
+ esa ciudad ni da acceso directo a ella:
1647
+
1648
+ ```bash
1649
+ agents-city connect --city producto --service https://connect.example.com --trust-file roots.json
1650
+ agents-city connect --city research --service https://connect.example.com --trust-file roots.json
1651
+ ```
1652
+
1653
+ Una persona solicita la conexión en el servicio y la otra la acepta. Los
1654
+ clientes reciben la Road bilateral mediante sus sesiones autenticadas; ninguna
1655
+ parte intercambia un token compartido, abre un puerto local ni recibe el catálogo
1656
+ de ciudades de la otra. El texto entrante se detiene primero en la recepción
1657
+ humana. Quien lo recibe decide qué ciudad o ciudades locales pueden leerlo, o
1658
+ activa el router opcional que falla de forma cerrada y sólo actúa cuando una
1659
+ regla coincide sin ambigüedad. El contrato del cliente público está en
1660
+ [docs/managed-connect.md](docs/managed-connect.md).
1661
+
1662
+ Para autoalojar el transporte remoto existente basado en token, intercambia en
1663
+ cambio las invitaciones públicas de ciudad. En la máquina A:
1578
1664
 
1579
1665
  ```bash
1580
1666
  agents-city road invite producto > producto.invitation.json
@@ -1803,12 +1889,32 @@ El hub local no mezcla datos efímeros con la configuración legible:
1803
1889
  ├── road-queue/*.json
1804
1890
  ├── road-inbox/*.json
1805
1891
  └── road-history.jsonl
1892
+
1893
+ ~/.agents-city/.runtime/reception/
1894
+ └── reception.sqlite3 # cuarentena del propietario compartida por sus ciudades
1806
1895
  ```
1807
1896
 
1808
1897
  Las credenciales y ficheros de runtime se crean con permisos privados. Los
1809
1898
  outboxes permiten que un actor se reconecte sin perder tareas ya aceptadas; el
1810
- ACK elimina el pendiente. El límite actual es 200 pendientes por cola y 72 horas
1811
- de vida por mensaje. `bus inbox` consume `road-inbox`, no el historial append-only.
1899
+ ACK elimina el pendiente. Los outboxes de actores y la cola local de reintentos
1900
+ admiten 200 pendientes; el inbox de Roads admite 500 por defecto y devuelve como
1901
+ máximo los 20 más antiguos en cada lectura. El texto E2EE gestionado entra
1902
+ primero en la recepción separada del propietario: ninguna ciudad ni modelo puede
1903
+ consumirlo hasta que una persona lo rechaza o lo envía a una o varias ciudades
1904
+ desde el ayuntamiento. Una ráfaga ya enrutada crea una sola activación agrupada
1905
+ del asiento, no un turno del modelo por mensaje, y cada runtime nativo ejecuta
1906
+ como máximo un turno a la vez. Una cola llena aplica backpressure en vez de
1907
+ borrar silenciosamente un elemento anterior. La vida de cada mensaje es de 72
1908
+ horas. `bus inbox` consume el `road-inbox` aprobado, no la cuarentena ni el
1909
+ historial append-only.
1910
+
1911
+ El rendimiento del relay no es el rendimiento de respuestas. Para una ciudad,
1912
+ la capacidad semántica segura es aproximadamente las peticiones agrupadas por
1913
+ turno divididas por la duración del turno. La regresión local vacía 100 mensajes
1914
+ de Road en cinco lotes exactos de 20 tras una sola activación sin contenido; una
1915
+ prueba separada con 20 peticiones y runtime lento demuestra que la concurrencia
1916
+ del modelo permanece en uno y que el backlog durable se vacía sin pérdidas. El
1917
+ resultado `queued` del remitente nunca significa leído ni respondido.
1812
1918
 
1813
1919
  ### Variables configurables
1814
1920
 
@@ -1830,6 +1936,12 @@ de vida por mensaje. `bus inbox` consume `road-inbox`, no el historial append-on
1830
1936
  | `CITY_HOOKS` | `city` | `everywhere` ejecuta los hooks de conciencia en todas las sesiones de Claude, no solo en runtimes de ciudad |
1831
1937
  | `CITY_DESKTOP` | `~/Desktop`, o el escritorio de Windows bajo WSL | dónde escribe `agents-city shortcut` |
1832
1938
  | `CITY_CAGE` | `1` | `0` arranca todas las ventanas sin jaula |
1939
+ | `CITY_ROAD_INBOX_MAX_PENDING` | `500` | capacidad local del inbox de Roads, entre 20 y 10.000; al llenarse aplica backpressure |
1940
+ | `CITY_ROAD_INBOX_WAKE_INTERVAL_MS` | `300000` | intervalo mínimo entre activaciones agrupadas del backlog, de 30 segundos a 1 hora |
1941
+ | `CITY_RECEPTION_MAX_PENDING` | `10000` | mensajes remotos pendientes del propietario antes de aplicar contrapresión al relay, de 100 a 100.000 |
1942
+ | `CITY_RECEPTION_MAX_BYTES` | `67108864` | bytes totales pendientes en la recepción local privada, de 1 MiB a 512 MiB |
1943
+ | `CITY_RECEPTION_PENDING_DAYS` | `30` | retención local de mensajes sin decidir, de 1 a 90 días |
1944
+ | `CITY_RECEPTION_DELIVERY_INTERVAL_MS` | `1000` | frecuencia con la que un bus reclama rutas aprobadas por la persona, de 250 ms a 30 segundos |
1833
1945
  | `CITY_CAGE_DENY` | vacío | rutas extra que sellar, separadas por `:` |
1834
1946
  | `CITY_CAGE_ALLOW_WRITE` | vacío | rutas extra que mantener escribibles, separadas por `:` |
1835
1947
  | `CITY_UPDATE_CHECK` | `1` | `0` no pregunta nunca a npm si hay versión más nueva |
@@ -1966,10 +2078,18 @@ tus repos, adjuntarse a tu tmux o leer ficheros privados de tu home. Para códig
1966
2078
  no confiable usa cuentas/VMs/contenedores separados y aplica también los permisos
1967
2079
  del CLI de cada proveedor.
1968
2080
 
1969
- El bus remoto amplía la superficie de confianza. Despliega HTTPS/WSS, rota
1970
- tokens, limita los scopes y revisa [docs/self-host.md](docs/self-host.md). Una
1971
- carretera autoriza intercambio de mensajes entre asientos; no implica confianza
1972
- para ejecutar comandos recibidos ni acceso al filesystem remoto.
2081
+ Un bus remoto amplía la superficie de confianza. Para el transporte autoalojado
2082
+ con token, despliega HTTPS/WSS, rota tokens, limita los scopes y revisa
2083
+ [docs/self-host.md](docs/self-host.md). Managed Connect usa firmas de
2084
+ dispositivo, transparencia de claves con testigos, establecimiento de sesión
2085
+ híbrido X25519 + ML-KEM-768, Double Ratchet de Olm y entrega sellada. Su material
2086
+ privado queda cifrado bajo una clave del keyring del sistema operativo en el
2087
+ directorio sellado por la jaula `~/.agents-city/.runtime/connect/vault/`; consulta
2088
+ [docs/managed-connect.md](docs/managed-connect.md). Una Road gestionada autoriza
2089
+ alcance cifrado hasta la recepción humana del propietario, no entrada directa a
2090
+ un modelo. Solo la ruta posterior del propietario deja el texto disponible para
2091
+ las ciudades elegidas. Ninguna Road autoriza ejecutar comandos recibidos ni
2092
+ acceder al filesystem remoto.
1973
2093
 
1974
2094
  ## Resolución de problemas
1975
2095
 
@@ -2131,6 +2251,115 @@ La detección exige `.git` (directorio o fichero de worktree) y un remote `origi
2131
2251
  Si acabas de cambiar las raíces, ejecuta `plugin/scripts/busca.py --refresh`. `AGENTS_CITY_ORG` puede estar filtrando el repo;
2132
2252
  déjala vacía para indexar todos los remotes.
2133
2253
 
2254
+ ### Tus CLIs, tal y como las tienes
2255
+
2256
+ Esto no compite con la CLI que ya usas. Las orquesta, y eso sólo funciona si
2257
+ respeta lo que configuraste en ellas — tus plugins, tus skills, tus servidores
2258
+ MCP, tu modelo, tus permisos.
2259
+
2260
+ Eso es una afirmación sobre tu máquina, así que va como comando y no como
2261
+ promesa:
2262
+
2263
+ ```bash
2264
+ agents-city doctor --config # qué añadimos, qué heredamos, qué no tocamos
2265
+ agents-city doctor --config --json # lo mismo, como datos
2266
+ ```
2267
+
2268
+ Imprime tres columnas por CLI, y la diferencia entre ellas es lo importante:
2269
+
2270
+ * **el trato** — lo que añadimos o pisamos. Es corto, cada línea dice *por qué*,
2271
+ y es lo que hace que el bus sea la única ruta entre agentes y que la jaula
2272
+ aguante. Sin eso no hay producto.
2273
+ * **heredamos** — lo que a propósito *no* mandamos, para que tu propia CLI lea
2274
+ tu propia configuración. Tu modelo, tu esfuerzo, tu política de aprobación.
2275
+ * **no tocamos** — lo que carga exactamente igual que siempre.
2276
+
2277
+ El informe y el runtime leen **el mismo fichero** —
2278
+ `plugin/channel/runtime/arnes.json` — así que la afirmación no puede separarse
2279
+ del comportamiento. Los conectores sacan sus valores de esa declaración en vez de
2280
+ escribirlos a mano, y la suite falla si un runtime impone algo que la declaración
2281
+ no menciona. Escribir esa comprobación encontró dos: un system prompt inyectado
2282
+ en Kimi que no declaraba nadie, y un valor de sandbox escrito en dos sitios.
2283
+
2284
+ Donde tu ajuste y el nuestro se cruzan, gana el tuyo cuando puede: el
2285
+ `approval_policy` de Codex se respeta si lo has puesto, y `on-request` es sólo el
2286
+ recurso cuando no. El informe dice la consecuencia en voz alta — `never`
2287
+ desactiva las herramientas de app y MCP — en vez de decidir en silencio que no
2288
+ querías decir eso.
2289
+
2290
+ ### Tu silla conserva tu propio Claude Code
2291
+
2292
+ La ventana del asiento abre **Claude Code de verdad** — tus plugins, tus skills,
2293
+ tus servidores MCP, tu statusline, el autocompletado de slash commands, el
2294
+ selector de modelo. Es el arnés que ya usas, en el panel, y es a propósito: la
2295
+ silla es donde una persona trabaja a mano.
2296
+
2297
+ Sigue estando en el bus. Los hooks del plugin (`SessionStart`,
2298
+ `UserPromptSubmit`, `Stop`, `SessionEnd`) reportan los prompts y las respuestas
2299
+ de esa sesión como los mismos eventos `conversation.*` que reporta el gateway,
2300
+ así que el ayuntamiento ve la conversación igual. Y lleva los dos flags que
2301
+ hacen del bus la única ruta entre agentes — `crossSessionInbound: refuse` y
2302
+ `--disallowed-tools SendMessage,ListAgents`. Un producto más silencioso con un
2303
+ agujero dentro no sería un producto mejor.
2304
+
2305
+ **Las casas de los agentes conservan el gateway** y su prompt `city>`, porque lo
2306
+ que compra el gateway es que el bus pueda *meter* trabajo en una ventana — que
2307
+ es el oficio entero de una casa y nada del oficio de la silla.
2308
+
2309
+ Una clave de la ficha devuelve la silla a lo de antes:
2310
+
2311
+ ```yaml
2312
+ ui.seat: gateway # el prompt de la ciudad en la silla, como antes
2313
+ ```
2314
+
2315
+ `CITY_UI=gateway` lo fuerza para un arranque. A las casas no se les pregunta:
2316
+ una casa existe para recibir encargos, y el gateway es lo que lo hace posible.
2317
+
2318
+ ### El motor con el que corre una casa
2319
+
2320
+ `model.<ventana>` y `effort.<ventana>` en la ficha dicen con qué corre una casa,
2321
+ una sola vez, la mueva la CLI que la mueva. Claude los toma como flags; los
2322
+ gateways nativos leen esa misma grafía del texto del comando y la mandan con el
2323
+ turno — por eso una clave significa lo mismo para las cuatro:
2324
+
2325
+ | proveedor | modelo | esfuerzo |
2326
+ | --- | --- | --- |
2327
+ | `claude` | sí, un alias que resuelve la CLI (`opus`, `sonnet`…) | sí |
2328
+ | `codex` | sí, el nombre que use tu Codex (`~/.codex/config.toml`) | sí |
2329
+ | `opencode` | sí, `proveedor/modelo` | no existe ese ajuste |
2330
+ | `kimi` | sí | no existe ese ajuste |
2331
+
2332
+ Un comando que ya lleva el flag se lo queda: `runs.dbt: codex --model o3` es
2333
+ alguien diciendo lo que quería, y una clave genérica no debe pisar una frase
2334
+ concreta. El esfuerzo sólo se escribe donde se lee, porque un flag que nadie lee
2335
+ es justo como un control acaba pareciendo que funciona.
2336
+
2337
+ ### Publicar una versión
2338
+
2339
+ Una release es una etiqueta. Empujar `v0.5.2` ejecuta la suite entera en Linux,
2340
+ macOS y Windows, comprueba que la etiqueta y los tres manifiestos dicen la misma
2341
+ versión, y publica con **procedencia**: una declaración firmada de qué commit y
2342
+ qué workflow produjeron ese tarball exacto. Cualquiera puede comprobarlo:
2343
+
2344
+ ```bash
2345
+ npm audit signatures
2346
+ ```
2347
+
2348
+ No hay ningún token guardado. Publica por *trusted publishing* de npm, que
2349
+ cambia una identidad OIDC de corta vida emitida por el workflow por el derecho a
2350
+ publicar este paquete: un secreto que no existe no se puede filtrar.
2351
+
2352
+ ```bash
2353
+ npm version patch --no-git-tag-version # y luego un PR con la subida
2354
+ git tag v0.5.2 && git push origin v0.5.2 # la etiqueta es la release
2355
+ ```
2356
+
2357
+ Esto existe porque publicar a mano no funcionaba. Cuatro versiones se quedaron
2358
+ sin publicar en un solo día, no por descuido sino porque el paso vivía en la
2359
+ cabeza de una persona y necesitaba su passkey — y lo que llegaba al registro era
2360
+ lo que hubiera en un directorio de trabajo, sin conexión con ningún commit que
2361
+ nadie pudiera nombrar.
2362
+
2134
2363
  ### Desinstalarlo del todo
2135
2364
 
2136
2365
  ```bash
package/README.md CHANGED
@@ -104,7 +104,7 @@ This is `0.x` on purpose: the commands are usable today, and the file formats
104
104
  and APIs can still change between minor versions. Nothing here pretends to be
105
105
  frozen yet.
106
106
 
107
- You need Node.js 22+, Python 3 and tmux; the
107
+ You need Node.js 22.13+, Python 3 and tmux; the
108
108
  [requirements table](#base-requirements) has the details, and `agents-city seat`
109
109
  offers to install tmux when it is missing. Nothing is installed system-wide
110
110
  beyond the npm global folder of your active Node installation.
@@ -160,7 +160,7 @@ agents-city --version
160
160
 
161
161
  | Requirement | Used for |
162
162
  |---|---|
163
- | Node.js 22 or later | npm package, WebSocket bus, and frontends |
163
+ | Node.js 22.13 or later | npm package, WebSocket bus, local reception, and frontends |
164
164
  | npm | installation and packaging |
165
165
  | Python 3 | Hall, onboarding, cities, maps, and utilities |
166
166
  | bash | sessions and launchers |
@@ -486,6 +486,7 @@ agents-city setup
486
486
  agents-city seat
487
487
  agents-city cities
488
488
  agents-city road
489
+ agents-city connect
489
490
  agents-city bus
490
491
  agents-city committee
491
492
  agents-city agents
@@ -576,6 +577,18 @@ explicit choice. Translations are keyed by the English sentence, so anything not
576
577
  yet translated falls back to readable English rather than to an identifier — new
577
578
  strings are never blocked on a translation pass.
578
579
 
580
+ Coverage is a test, not a habit. `bin/test-i18n.py` reads the render paths, pulls
581
+ out every English sentence a person will see, and fails when one has no Spanish —
582
+ so a new view cannot quietly ship untranslated, which is how coverage had drifted
583
+ to about 40% before anybody noticed.
584
+
585
+ The obvious mechanism, sweeping the rendered DOM and translating what matches a
586
+ key, is deliberately **not** what this does. At DOM time there is no way to tell
587
+ a sentence this product wrote from a city or agent name somebody typed, so a
588
+ person whose city is called `Overview` would watch it rename itself. The
589
+ distinction only exists in the source, between a literal and an interpolation,
590
+ and that is where the check is made: anything holding a `${}` is skipped.
591
+
579
592
  ### `agents-city setup`
580
593
 
581
594
  Creates or selects a city and opens the Hall; `--tui` hands the flow to `seat`.
@@ -681,6 +694,62 @@ agents-city road disconnect product <remote-city-id>
681
694
  A city cannot connect to itself. Each machine must independently accept the
682
695
  other remote invitation.
683
696
 
697
+ ### `agents-city connect`
698
+
699
+ Pairs this computer with a managed Road service. It does not create a
700
+ connection unilaterally: both people approve it in the service, and the
701
+ recipient sees the sender in their private human reception without exposing a
702
+ city catalogue. The public client implements protocol v4; the hosted service is
703
+ outside this repository and is not production-enabled or independently audited.
704
+
705
+ ```bash
706
+ agents-city connect --service https://connect.example.com --trust-file roots.json
707
+ agents-city connect --city product
708
+ agents-city connect --all
709
+ agents-city connect status
710
+ agents-city connect roads
711
+ ```
712
+
713
+ The command generates Ed25519/X25519, Olm and signed ML-KEM-768 material on this
714
+ computer, prints a one-use PASCO and opens the browser for approval. Only public
715
+ material is uploaded. Private keys, ratchet state, ML-KEM seeds and retry data
716
+ are encrypted in `~/.agents-city/.runtime/connect/vault/`; the wrapping key
717
+ stays in macOS Keychain, Windows Credential Manager or Linux Secret Service.
718
+ The client fails closed if that keyring is unavailable. The vault is sealed from
719
+ repo-agent windows on macOS and Linux.
720
+
721
+ The signed root chain supplied through `--trust-file` is mandatory for first
722
+ pairing with a non-development service. The client persists its last accepted
723
+ version. A later root must continue from that exact local root and carry enough
724
+ signatures from both the old and new offline authorities; skipped versions,
725
+ rollback, expiry and silent operator/witness replacement are rejected. Protocol
726
+ v4 then verifies the peer through key transparency, protects the first Olm
727
+ message with hybrid X25519 + ML-KEM-768, and uses the Olm Double Ratchet. Normal
728
+ sealed submissions omit sender, device, city and Road identity from the outer
729
+ request. This does not hide IP address, timing or padded size from Cloudflare,
730
+ and later ratchet steps are classical.
731
+
732
+ The package includes a public root only for the exact managed sandbox origin;
733
+ self-hosted services still require their reviewed `--trust-file`. A root
734
+ returned by the service is never accepted as a first pin.
735
+
736
+ `--city` chooses a local hub that can keep the computer's reception bridge
737
+ alive; it is not a recipient selector and is never disclosed to the other
738
+ person. Exactly one hub per computer holds the lease and one outbound encrypted
739
+ session; no public port is opened. Use `--service URL` or
740
+ `AGENTS_CITY_CONNECT_URL` for a pilot endpoint. The hosted server is not part of
741
+ this Apache repository; the auditable client and wire protocol are.
742
+
743
+ `agents-city connect roads` prints a connected person's name for a person Road,
744
+ not the opaque `rx-*` transport endpoints. In the Hall, every incoming message
745
+ waits for manual review by default. The owner may route it to one or more local
746
+ cities, reject it with a reason, or explicitly enable the deterministic Auto
747
+ router. Auto routes only one unique low-risk rule match; ambiguous, unmatched,
748
+ prompt-like, secret-seeking, or command-like text remains in the human queue.
749
+
750
+ See [docs/managed-connect.md](docs/managed-connect.md) for the exact key,
751
+ envelope, encryption, ACK, revocation and threat-model contract.
752
+
684
753
  ### `agents-city bus`
685
754
 
686
755
  Operates messages between seats over declared roads.
@@ -695,7 +764,7 @@ agents-city bus send '*' "Notice for every connected city"
695
764
  | Subcommand | Effect |
696
765
  |---|---|
697
766
  | `roster` | return roads and known online presence |
698
- | `inbox` | return and consume pending inbox; append-only history remains |
767
+ | `inbox` | return and consume the next approved batch of up to 20; managed text is unavailable until the owner routes it in the Hall |
699
768
  | `send owner/city TEXT` | send to one allowed destination |
700
769
  | `send '*' TEXT` | send to all roads; requires at least one |
701
770
 
@@ -1550,9 +1619,28 @@ AGENTS_CITY_DATA="$HOME/.agents-city/$CITY_OWNER/product" \
1550
1619
  Inside a normal session, you do not need to set `AGENTS_CITY_DATA`; it is already
1551
1620
  injected into each window. The example makes it explicit for an outside terminal.
1552
1621
 
1553
- ### Case 10: connect cities belonging to different machines or people
1622
+ ### Case 10: connect two people on different machines
1554
1623
 
1555
- On machine A:
1624
+ With a managed Road operator, each person pairs a computer. `--city` chooses the
1625
+ local hub that will start the owner-level reception bridge; it does not reveal
1626
+ that city or give the other person direct access to it:
1627
+
1628
+ ```bash
1629
+ agents-city connect --city product --service https://connect.example.com --trust-file roots.json
1630
+ agents-city connect --city research --service https://connect.example.com --trust-file roots.json
1631
+ ```
1632
+
1633
+ One person requests the connection in that service and the other accepts it.
1634
+ The clients learn the active bilateral person Road over their authenticated
1635
+ relay sessions; neither side exchanges a shared bus token, exposes a local
1636
+ port, or receives the other person's city catalogue. Incoming text first stops
1637
+ in the human reception. The recipient decides which local city or cities may
1638
+ read it, or lets the optional fail-closed rule router decide when one match is
1639
+ unambiguous. The public client contract is documented in
1640
+ [docs/managed-connect.md](docs/managed-connect.md).
1641
+
1642
+ To self-host the existing token-based remote transport instead, exchange the
1643
+ public city invitations manually. On machine A:
1556
1644
 
1557
1645
  ```bash
1558
1646
  agents-city road invite product > product.invitation.json
@@ -1780,12 +1868,29 @@ The local hub keeps ephemeral state separate from readable configuration:
1780
1868
  ├── road-queue/*.json
1781
1869
  ├── road-inbox/*.json
1782
1870
  └── road-history.jsonl
1871
+
1872
+ ~/.agents-city/.runtime/reception/
1873
+ └── reception.sqlite3 # owner quarantine shared by local cities
1783
1874
  ```
1784
1875
 
1785
1876
  Credentials and runtime files are created with private permissions. Outboxes let
1786
1877
  an actor reconnect without losing an already accepted task; its ACK removes the
1787
- pending item. Current limits are 200 pending items per queue and a 72-hour
1788
- message lifetime. `bus inbox` consumes `road-inbox`, not append-only history.
1878
+ pending item. Actor outboxes and the local retry queue admit 200 pending items;
1879
+ the Road inbox admits 500 by default and returns at most 20 oldest items per
1880
+ read. Managed E2EE text first enters the separate owner reception: no city or
1881
+ model can consume it until a person rejects it or routes it to one or more
1882
+ cities in the Hall. A routed burst creates one coalesced seat wake-up rather
1883
+ than one model turn per message, and every native runtime runs at most one turn
1884
+ at a time. Full queues apply backpressure instead of silently deleting an older
1885
+ item. Message lifetime is 72 hours. `bus inbox` consumes approved `road-inbox`,
1886
+ not reception quarantine or append-only history.
1887
+
1888
+ Relay throughput is not answer throughput. For one city, safe semantic capacity
1889
+ is approximately grouped requests per turn divided by turn duration. The local
1890
+ regression drains 100 Road messages in five exact batches of 20 after one
1891
+ content-free wake-up; a separate 20-request slow-runtime test proves model
1892
+ concurrency stays at one and the durable backlog drains without loss. A sender's
1893
+ `queued` result never means read or answered.
1789
1894
 
1790
1895
  ### Configurable variables
1791
1896
 
@@ -1807,6 +1912,12 @@ message lifetime. `bus inbox` consumes `road-inbox`, not append-only history.
1807
1912
  | `CITY_HOOKS` | `city` | `everywhere` runs the conscience hooks in every Claude session, not only city runtimes |
1808
1913
  | `CITY_DESKTOP` | `~/Desktop`, or the Windows desktop under WSL | where `agents-city shortcut` writes |
1809
1914
  | `CITY_CAGE` | `1` | `0` launches every window uncaged |
1915
+ | `CITY_ROAD_INBOX_MAX_PENDING` | `500` | local Road inbox capacity, from 20 to 10,000; a full inbox applies backpressure |
1916
+ | `CITY_ROAD_INBOX_WAKE_INTERVAL_MS` | `300000` | minimum interval between coalesced backlog wake-ups, from 30 seconds to 1 hour |
1917
+ | `CITY_RECEPTION_MAX_PENDING` | `10000` | owner-level pending remote messages before relay backpressure, from 100 to 100,000 |
1918
+ | `CITY_RECEPTION_MAX_BYTES` | `67108864` | total pending plaintext bytes in private local reception, from 1 MiB to 512 MiB |
1919
+ | `CITY_RECEPTION_PENDING_DAYS` | `30` | undecided local-message retention, from 1 to 90 days |
1920
+ | `CITY_RECEPTION_DELIVERY_INTERVAL_MS` | `1000` | how often a city bus claims human-approved routes, from 250 ms to 30 seconds |
1810
1921
  | `CITY_CAGE_DENY` | empty | extra colon-separated paths to seal |
1811
1922
  | `CITY_CAGE_ALLOW_WRITE` | empty | extra colon-separated paths to keep writable |
1812
1923
  | `CITY_UPDATE_CHECK` | `1` | `0` never asks npm whether a newer version exists |
@@ -1936,10 +2047,18 @@ read your repos, attach to tmux, or read private files in your home. Use separat
1936
2047
  accounts, VMs, or containers for untrusted code, and also apply each provider
1937
2048
  CLI's permission controls.
1938
2049
 
1939
- A remote bus expands the trust surface. Deploy HTTPS/WSS, rotate tokens, limit
1940
- scopes, and read [docs/self-host.md](docs/self-host.md). A road authorises message
1941
- exchange between seats; it neither authorises execution of received commands nor
1942
- grants remote filesystem access.
2050
+ A remote bus expands the trust surface. For the self-hosted token transport,
2051
+ deploy HTTPS/WSS, rotate tokens, limit scopes, and read
2052
+ [docs/self-host.md](docs/self-host.md). Managed Connect instead uses device
2053
+ signatures, witnessed key transparency, hybrid X25519 + ML-KEM-768 session
2054
+ establishment, an Olm Double Ratchet and sealed delivery. Its private material
2055
+ is encrypted under an OS-keyring wrapping key in the cage-sealed
2056
+ `~/.agents-city/.runtime/connect/vault/` directory; see
2057
+ [docs/managed-connect.md](docs/managed-connect.md). A managed Road authorises
2058
+ encrypted reachability to the owner's human reception, not direct model input.
2059
+ Only the owner's later route makes the text available to selected cities. No
2060
+ Road authorises execution of received commands or grants remote filesystem
2061
+ access.
1943
2062
 
1944
2063
  ## Troubleshooting
1945
2064
 
@@ -2096,6 +2215,114 @@ Discovery requires `.git` (a directory or worktree file) and an `origin` remote.
2096
2215
  If roots just changed, run `plugin/scripts/busca.py --refresh`. `AGENTS_CITY_ORG` may be filtering the repo;
2097
2216
  leave it empty to index every remote.
2098
2217
 
2218
+ ### Your CLIs, as you have them
2219
+
2220
+ This does not compete with the CLI you already run. It orchestrates them, which
2221
+ only works if it respects what you configured in them — your plugins, your
2222
+ skills, your MCP servers, your model, your permissions.
2223
+
2224
+ That is a claim about your machine, so it ships as a command rather than a
2225
+ promise:
2226
+
2227
+ ```bash
2228
+ agents-city doctor --config # what we add, inherit and leave alone
2229
+ agents-city doctor --config --json # the same, as data
2230
+ ```
2231
+
2232
+ It prints three columns per CLI, and the difference between them is the point:
2233
+
2234
+ * **the deal** — what we add or override. It is short, every line says *why*,
2235
+ and it is what makes the bus the only route between agents and the cage hold.
2236
+ Without it there is no product.
2237
+ * **we inherit** — what we deliberately do *not* send, so your own CLI reads
2238
+ your own configuration for it. Your model, your effort, your approval policy.
2239
+ * **untouched** — what loads exactly as it always did.
2240
+
2241
+ The report and the runtime read the **same file** — `plugin/channel/runtime/arnes.json`
2242
+ — so the claim cannot drift from the behaviour. The connectors take their policy
2243
+ values out of that declaration instead of spelling them inline, and the suite
2244
+ fails if a runtime imposes something the declaration does not mention. Writing
2245
+ that check found two: a system prompt injected into Kimi that nothing declared,
2246
+ and a sandbox value written in two places.
2247
+
2248
+ Where your setting and ours meet, yours wins where it can: Codex's
2249
+ `approval_policy` is honoured when you set one, and `on-request` is only the
2250
+ fallback when you have not. The report says the consequence out loud — `never`
2251
+ disables app and MCP tools — instead of quietly deciding you did not mean it.
2252
+
2253
+ ### Your chair keeps your own Claude Code
2254
+
2255
+ The seat window opens **Claude Code itself** — your plugins, your skills, your
2256
+ MCP servers, your statusline, slash-command completion, the model picker. It is
2257
+ the harness you already use, in the pane, and that is deliberate: the chair is
2258
+ where a person works by hand.
2259
+
2260
+ It is still on the bus. The city plugin's `SessionStart`, `UserPromptSubmit`,
2261
+ `Stop` and `SessionEnd` hooks report that session's prompts and answers as the
2262
+ same `conversation.*` events the gateway reports, so the town hall sees the
2263
+ conversation either way. And it carries the same two flags that make the bus the
2264
+ only route between agents — `crossSessionInbound: refuse` and
2265
+ `--disallowed-tools SendMessage,ListAgents`. A quieter product with a hole in it
2266
+ would not be a better product.
2267
+
2268
+ **Agent houses keep the gateway** and its `city>` prompt, because what the
2269
+ gateway buys is the bus being able to *push* work into a window — which is the
2270
+ whole job of a house and no part of the chair's.
2271
+
2272
+ One card key moves the chair back:
2273
+
2274
+ ```yaml
2275
+ ui.seat: gateway # the city's own prompt in the chair, as before
2276
+ ```
2277
+
2278
+ `CITY_UI=gateway` forces it for one launch. Houses are not asked: a house exists
2279
+ to receive assignments, and the gateway is what makes that possible.
2280
+
2281
+ ### The engine a house runs on
2282
+
2283
+ `model.<window>` and `effort.<window>` on the card say what a house runs on, once,
2284
+ whatever CLI runs it. Claude takes them as flags; the native gateways parse the
2285
+ same spelling out of the command string and send it with the turn — which is why
2286
+ one key means the same thing for all four:
2287
+
2288
+ | provider | model | effort |
2289
+ | --- | --- | --- |
2290
+ | `claude` | yes, an alias the CLI resolves (`opus`, `sonnet`…) | yes |
2291
+ | `codex` | yes, the name your Codex uses (`~/.codex/config.toml`) | yes |
2292
+ | `opencode` | yes, `provider/model` | no such setting |
2293
+ | `kimi` | yes | no such setting |
2294
+
2295
+ A command that already carries the flag keeps it: `runs.dbt: codex --model o3`
2296
+ was somebody saying what they meant, and a generic key must not overrule a
2297
+ specific sentence. Effort is written only where it is read, because a flag
2298
+ nothing reads is how a control ends up looking like it works.
2299
+
2300
+ ### Releasing
2301
+
2302
+ A release is a tag. Pushing `v0.5.2` runs the whole suite on Linux, macOS and
2303
+ Windows, checks that the tag and the three manifests name the same version, and
2304
+ publishes with **provenance** — a signed statement of which commit and which
2305
+ workflow produced that exact tarball. Anyone can check it:
2306
+
2307
+ ```bash
2308
+ npm audit signatures
2309
+ ```
2310
+
2311
+ No token is stored anywhere. It publishes through npm's trusted publishing,
2312
+ which trades a short-lived OIDC identity from the workflow for the right to
2313
+ publish this one package: a secret that does not exist cannot leak.
2314
+
2315
+ ```bash
2316
+ npm version patch --no-git-tag-version # then open a PR with the bump
2317
+ git tag v0.5.2 && git push origin v0.5.2 # the tag is the release
2318
+ ```
2319
+
2320
+ This exists because publishing by hand did not work. Four versions went
2321
+ unpublished in a single day, not because anybody was careless but because the
2322
+ step lived in a person's head and needed their passkey — and what reached the
2323
+ registry was whatever happened to be in a working directory, connected to no
2324
+ commit anyone could name.
2325
+
2099
2326
  ### Removing it completely
2100
2327
 
2101
2328
  ```bash