agents-city 0.3.0-beta.21

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 (440) hide show
  1. package/.claude-plugin/marketplace.json +16 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +5 -0
  4. package/README.es.md +2021 -0
  5. package/README.md +1999 -0
  6. package/benchmarks/committee/README.md +39 -0
  7. package/benchmarks/committee/metrics.py +51 -0
  8. package/benchmarks/committee/run.py +64 -0
  9. package/benchmarks/committee/traces/01-single.json +8 -0
  10. package/benchmarks/committee/traces/02-mesh.json +16 -0
  11. package/benchmarks/committee/traces/03-chair.json +15 -0
  12. package/benchmarks/latency/README.md +35 -0
  13. package/benchmarks/latency/fake-claude-cli.mjs +119 -0
  14. package/benchmarks/latency/fake-native-server.mjs +340 -0
  15. package/benchmarks/latency/fake-tui.py +91 -0
  16. package/benchmarks/latency/live.py +380 -0
  17. package/benchmarks/stress/README.md +20 -0
  18. package/benchmarks/stress/run.py +487 -0
  19. package/bin/agents +4 -0
  20. package/bin/agents-city.js +116 -0
  21. package/bin/benchmark +17 -0
  22. package/bin/bus +6 -0
  23. package/bin/cities +3 -0
  24. package/bin/city +86 -0
  25. package/bin/committee +6 -0
  26. package/bin/demo +200 -0
  27. package/bin/exit +5 -0
  28. package/bin/hall +73 -0
  29. package/bin/hall.html +426 -0
  30. package/bin/logs +3 -0
  31. package/bin/report.py +14 -0
  32. package/bin/reset +3 -0
  33. package/bin/road +3 -0
  34. package/bin/seat +10 -0
  35. package/bin/serve.py +1428 -0
  36. package/bin/setup.py +753 -0
  37. package/bin/skills +3 -0
  38. package/bin/test +131 -0
  39. package/bin/test-adapter.py +183 -0
  40. package/bin/test-admision.py +99 -0
  41. package/bin/test-avatar.py +58 -0
  42. package/bin/test-benchmark.py +40 -0
  43. package/bin/test-broker.py +334 -0
  44. package/bin/test-cage.py +206 -0
  45. package/bin/test-card.py +402 -0
  46. package/bin/test-channel.py +379 -0
  47. package/bin/test-cities.py +453 -0
  48. package/bin/test-claude-runtime.py +252 -0
  49. package/bin/test-committee.py +340 -0
  50. package/bin/test-contracts.py +544 -0
  51. package/bin/test-crecimiento.py +164 -0
  52. package/bin/test-demo.py +308 -0
  53. package/bin/test-doctor.py +106 -0
  54. package/bin/test-domains.py +151 -0
  55. package/bin/test-evidencia.py +50 -0
  56. package/bin/test-exit.py +128 -0
  57. package/bin/test-hall-protocol.py +93 -0
  58. package/bin/test-launch.py +169 -0
  59. package/bin/test-live-feed.py +508 -0
  60. package/bin/test-pairing.py +99 -0
  61. package/bin/test-parcels.py +283 -0
  62. package/bin/test-runtime-failures.py +252 -0
  63. package/bin/test-runtime-ui.py +488 -0
  64. package/bin/test-runtime.py +268 -0
  65. package/bin/test-rutas.py +80 -0
  66. package/bin/test-seat.py +1386 -0
  67. package/bin/test-security.py +150 -0
  68. package/bin/test-serve.py +1057 -0
  69. package/bin/test-stress.py +64 -0
  70. package/bin/test-widgets.py +143 -0
  71. package/bin/test-workspace.py +188 -0
  72. package/bin/testlib.py +95 -0
  73. package/bin/tokens.py +14 -0
  74. package/bus/scripts/deploy.sh +60 -0
  75. package/bus/scripts/mint-token.sh +68 -0
  76. package/bus/scripts/setup-dev.sh +79 -0
  77. package/bus/scripts/test-channel.ts +8 -0
  78. package/bus/scripts/test-hub.ts +266 -0
  79. package/bus/scripts/test-local.sh +40 -0
  80. package/bus/scripts/test-queue.ts +132 -0
  81. package/bus/worker/package-lock.json +1971 -0
  82. package/bus/worker/package.json +17 -0
  83. package/bus/worker/src/index.ts +485 -0
  84. package/bus/worker/tsconfig.json +13 -0
  85. package/bus/worker/wrangler.toml +32 -0
  86. package/city/oven/README.md +59 -0
  87. package/city/oven/collect.py +43 -0
  88. package/city/oven/oven.html +4 -0
  89. package/city/scripts/history.py +120 -0
  90. package/city/scripts/seed.py +442 -0
  91. package/city/web/assets/modelos/License.txt +28 -0
  92. package/city/web/assets/modelos/PROCEDENCIA.txt +3 -0
  93. package/city/web/assets/modelos/building-a.glb +0 -0
  94. package/city/web/assets/modelos/building-b.glb +0 -0
  95. package/city/web/assets/modelos/building-c.glb +0 -0
  96. package/city/web/assets/modelos/building-d.glb +0 -0
  97. package/city/web/assets/modelos/building-e.glb +0 -0
  98. package/city/web/assets/modelos/building-f.glb +0 -0
  99. package/city/web/assets/modelos/building-g.glb +0 -0
  100. package/city/web/assets/modelos/building-h.glb +0 -0
  101. package/city/web/assets/sprites/building-a.png +0 -0
  102. package/city/web/assets/sprites/building-b.png +0 -0
  103. package/city/web/assets/sprites/building-c.png +0 -0
  104. package/city/web/assets/sprites/building-d.png +0 -0
  105. package/city/web/assets/sprites/building-e.png +0 -0
  106. package/city/web/assets/sprites/building-f.png +0 -0
  107. package/city/web/assets/sprites/building-g.png +0 -0
  108. package/city/web/assets/sprites/building-h.png +0 -0
  109. package/city/web/assets/sprites/building-i.png +0 -0
  110. package/city/web/assets/sprites/building-j.png +0 -0
  111. package/city/web/assets/sprites/building-k.png +0 -0
  112. package/city/web/assets/sprites/building-l.png +0 -0
  113. package/city/web/assets/sprites/building-m.png +0 -0
  114. package/city/web/assets/sprites/building-n.png +0 -0
  115. package/city/web/assets/sprites/building-skyscraper-a.png +0 -0
  116. package/city/web/assets/sprites/building-skyscraper-b.png +0 -0
  117. package/city/web/assets/sprites/building-skyscraper-c.png +0 -0
  118. package/city/web/assets/sprites/building-skyscraper-d.png +0 -0
  119. package/city/web/assets/sprites/building-skyscraper-e.png +0 -0
  120. package/city/web/assets/sprites/catalogo.json +47 -0
  121. package/city/web/assets/sprites/detail-awning-wide.png +0 -0
  122. package/city/web/assets/sprites/detail-awning.png +0 -0
  123. package/city/web/assets/sprites/detail-overhang-wide.png +0 -0
  124. package/city/web/assets/sprites/detail-overhang.png +0 -0
  125. package/city/web/assets/sprites/detail-parasol-a.png +0 -0
  126. package/city/web/assets/sprites/detail-parasol-b.png +0 -0
  127. package/city/web/assets/sprites/low-detail-building-a.png +0 -0
  128. package/city/web/assets/sprites/low-detail-building-b.png +0 -0
  129. package/city/web/assets/sprites/low-detail-building-c.png +0 -0
  130. package/city/web/assets/sprites/low-detail-building-d.png +0 -0
  131. package/city/web/assets/sprites/low-detail-building-e.png +0 -0
  132. package/city/web/assets/sprites/low-detail-building-f.png +0 -0
  133. package/city/web/assets/sprites/low-detail-building-g.png +0 -0
  134. package/city/web/assets/sprites/low-detail-building-h.png +0 -0
  135. package/city/web/assets/sprites/low-detail-building-i.png +0 -0
  136. package/city/web/assets/sprites/low-detail-building-j.png +0 -0
  137. package/city/web/assets/sprites/low-detail-building-k.png +0 -0
  138. package/city/web/assets/sprites/low-detail-building-l.png +0 -0
  139. package/city/web/assets/sprites/low-detail-building-m.png +0 -0
  140. package/city/web/assets/sprites/low-detail-building-n.png +0 -0
  141. package/city/web/assets/sprites/low-detail-building-wide-a.png +0 -0
  142. package/city/web/assets/sprites/low-detail-building-wide-b.png +0 -0
  143. package/city/web/assets/sprites/medidas.json +248 -0
  144. package/city/web/dist/city.js +939 -0
  145. package/city/web/dist/index.html +294 -0
  146. package/city/web/dist/modelos/License.txt +28 -0
  147. package/city/web/dist/modelos/PROCEDENCIA.txt +3 -0
  148. package/city/web/dist/modelos/building-a.glb +0 -0
  149. package/city/web/dist/modelos/building-b.glb +0 -0
  150. package/city/web/dist/modelos/building-c.glb +0 -0
  151. package/city/web/dist/modelos/building-d.glb +0 -0
  152. package/city/web/dist/modelos/building-e.glb +0 -0
  153. package/city/web/dist/modelos/building-f.glb +0 -0
  154. package/city/web/dist/modelos/building-g.glb +0 -0
  155. package/city/web/dist/modelos/building-h.glb +0 -0
  156. package/city/web/dist/sprites/building-a.png +0 -0
  157. package/city/web/dist/sprites/building-b.png +0 -0
  158. package/city/web/dist/sprites/building-c.png +0 -0
  159. package/city/web/dist/sprites/building-d.png +0 -0
  160. package/city/web/dist/sprites/building-e.png +0 -0
  161. package/city/web/dist/sprites/building-f.png +0 -0
  162. package/city/web/dist/sprites/building-g.png +0 -0
  163. package/city/web/dist/sprites/building-h.png +0 -0
  164. package/city/web/dist/sprites/building-i.png +0 -0
  165. package/city/web/dist/sprites/building-j.png +0 -0
  166. package/city/web/dist/sprites/building-k.png +0 -0
  167. package/city/web/dist/sprites/building-l.png +0 -0
  168. package/city/web/dist/sprites/building-m.png +0 -0
  169. package/city/web/dist/sprites/building-n.png +0 -0
  170. package/city/web/dist/sprites/building-skyscraper-a.png +0 -0
  171. package/city/web/dist/sprites/building-skyscraper-b.png +0 -0
  172. package/city/web/dist/sprites/building-skyscraper-c.png +0 -0
  173. package/city/web/dist/sprites/building-skyscraper-d.png +0 -0
  174. package/city/web/dist/sprites/building-skyscraper-e.png +0 -0
  175. package/city/web/dist/sprites/catalogo.json +47 -0
  176. package/city/web/dist/sprites/detail-awning-wide.png +0 -0
  177. package/city/web/dist/sprites/detail-awning.png +0 -0
  178. package/city/web/dist/sprites/detail-overhang-wide.png +0 -0
  179. package/city/web/dist/sprites/detail-overhang.png +0 -0
  180. package/city/web/dist/sprites/detail-parasol-a.png +0 -0
  181. package/city/web/dist/sprites/detail-parasol-b.png +0 -0
  182. package/city/web/dist/sprites/low-detail-building-a.png +0 -0
  183. package/city/web/dist/sprites/low-detail-building-b.png +0 -0
  184. package/city/web/dist/sprites/low-detail-building-c.png +0 -0
  185. package/city/web/dist/sprites/low-detail-building-d.png +0 -0
  186. package/city/web/dist/sprites/low-detail-building-e.png +0 -0
  187. package/city/web/dist/sprites/low-detail-building-f.png +0 -0
  188. package/city/web/dist/sprites/low-detail-building-g.png +0 -0
  189. package/city/web/dist/sprites/low-detail-building-h.png +0 -0
  190. package/city/web/dist/sprites/low-detail-building-i.png +0 -0
  191. package/city/web/dist/sprites/low-detail-building-j.png +0 -0
  192. package/city/web/dist/sprites/low-detail-building-k.png +0 -0
  193. package/city/web/dist/sprites/low-detail-building-l.png +0 -0
  194. package/city/web/dist/sprites/low-detail-building-m.png +0 -0
  195. package/city/web/dist/sprites/low-detail-building-n.png +0 -0
  196. package/city/web/dist/sprites/low-detail-building-wide-a.png +0 -0
  197. package/city/web/dist/sprites/low-detail-building-wide-b.png +0 -0
  198. package/city/web/dist/sprites/medidas.json +248 -0
  199. package/city/web/dist-hall/hall.js +264 -0
  200. package/city/web/index.html +294 -0
  201. package/city/web/package.json +18 -0
  202. package/city/web/sella.py +21 -0
  203. package/city/web/src/activity-actors.ts +47 -0
  204. package/city/web/src/activity.ts +172 -0
  205. package/city/web/src/ayuntamiento.ts +580 -0
  206. package/city/web/src/draw.ts +640 -0
  207. package/city/web/src/game-speech.ts +79 -0
  208. package/city/web/src/hall.ts +1867 -0
  209. package/city/web/src/main.ts +2225 -0
  210. package/city/web/src/oven.ts +124 -0
  211. package/city/web/src/people.ts +244 -0
  212. package/city/web/src/presencia.ts +193 -0
  213. package/city/web/src/puertas.ts +180 -0
  214. package/city/web/src/repo-roles.ts +59 -0
  215. package/city/worker/package.json +16 -0
  216. package/city/worker/schema.sql +117 -0
  217. package/city/worker/src/index.ts +702 -0
  218. package/city/worker/src/square.ts +218 -0
  219. package/city/worker/wrangler.toml +58 -0
  220. package/demo/ada.md +50 -0
  221. package/demo/bruno.md +62 -0
  222. package/demo/camila.md +56 -0
  223. package/demo/city/ada.md +40 -0
  224. package/demo/city/city.yml +8 -0
  225. package/demo/city/parcels.yml +15 -0
  226. package/demo/city/roads.json +4 -0
  227. package/demo/city/units.yml +10 -0
  228. package/demo/clinica/city.yml +8 -0
  229. package/demo/clinica/parcels.yml +15 -0
  230. package/demo/clinica/roads.json +4 -0
  231. package/demo/clinica/units.yml +9 -0
  232. package/demo/clinica/vera.md +46 -0
  233. package/demo/dante.md +54 -0
  234. package/demo/despacho/city.yml +8 -0
  235. package/demo/despacho/marta.md +46 -0
  236. package/demo/despacho/parcels.yml +15 -0
  237. package/demo/despacho/roads.json +4 -0
  238. package/demo/despacho/units.yml +9 -0
  239. package/demo/elsa.md +50 -0
  240. package/demo/farid.md +52 -0
  241. package/demo/greta.md +50 -0
  242. package/demo/hugo.md +49 -0
  243. package/demo/iris.md +49 -0
  244. package/demo/jonas.md +51 -0
  245. package/demo/kira.md +51 -0
  246. package/demo/luca.md +49 -0
  247. package/demo/parcels.yml +92 -0
  248. package/demo/seed.py +20 -0
  249. package/demo/show.py +161 -0
  250. package/demo/stories.py +824 -0
  251. package/demo/units.yml +33 -0
  252. package/docs/agents-first.md +99 -0
  253. package/docs/glossary.md +55 -0
  254. package/docs/map-live-layers.md +73 -0
  255. package/docs/security.md +231 -0
  256. package/docs/self-host.md +151 -0
  257. package/docs/testing.md +205 -0
  258. package/package.json +73 -0
  259. package/plugin/.claude-plugin/plugin.json +62 -0
  260. package/plugin/.mcp.json +15 -0
  261. package/plugin/channel/activity-cli.ts +16 -0
  262. package/plugin/channel/adapter-prompts.ts +113 -0
  263. package/plugin/channel/adapter.js +4292 -0
  264. package/plugin/channel/adapter.ts +133 -0
  265. package/plugin/channel/bus.js +20188 -0
  266. package/plugin/channel/bus.ts +131 -0
  267. package/plugin/channel/city-config.ts +125 -0
  268. package/plugin/channel/claude-channel.ts +58 -0
  269. package/plugin/channel/cli-args.ts +75 -0
  270. package/plugin/channel/client.js +4200 -0
  271. package/plugin/channel/client.ts +38 -0
  272. package/plugin/channel/committee/activity.ts +256 -0
  273. package/plugin/channel/committee/collection.ts +154 -0
  274. package/plugin/channel/committee/decision.ts +175 -0
  275. package/plugin/channel/committee/floor.ts +135 -0
  276. package/plugin/channel/committee/guards.ts +30 -0
  277. package/plugin/channel/committee/history.ts +38 -0
  278. package/plugin/channel/committee/render.ts +102 -0
  279. package/plugin/channel/committee/service.ts +72 -0
  280. package/plugin/channel/committee/storage.ts +92 -0
  281. package/plugin/channel/committee/types.ts +163 -0
  282. package/plugin/channel/committee/view.ts +63 -0
  283. package/plugin/channel/committee-cli.ts +109 -0
  284. package/plugin/channel/delivery-metrics.ts +61 -0
  285. package/plugin/channel/delivery-queue.ts +118 -0
  286. package/plugin/channel/hub/activity-controller.ts +72 -0
  287. package/plugin/channel/hub/activity-feed.ts +169 -0
  288. package/plugin/channel/hub/committee-controller.ts +60 -0
  289. package/plugin/channel/hub/connections.ts +48 -0
  290. package/plugin/channel/hub/diagnostics.ts +67 -0
  291. package/plugin/channel/hub/envelope-validity.ts +109 -0
  292. package/plugin/channel/hub/envelopes.ts +66 -0
  293. package/plugin/channel/hub/lifecycle.ts +91 -0
  294. package/plugin/channel/hub/local-roads.ts +89 -0
  295. package/plugin/channel/hub/remote-roads.ts +165 -0
  296. package/plugin/channel/hub/road-controller.ts +99 -0
  297. package/plugin/channel/hub-client.ts +149 -0
  298. package/plugin/channel/local-hub.js +5995 -0
  299. package/plugin/channel/local-hub.ts +349 -0
  300. package/plugin/channel/map-reporter.ts +48 -0
  301. package/plugin/channel/package-lock.json +1731 -0
  302. package/plugin/channel/package.json +19 -0
  303. package/plugin/channel/protocol.ts +98 -0
  304. package/plugin/channel/road-cli.ts +18 -0
  305. package/plugin/channel/run.sh +15 -0
  306. package/plugin/channel/runtime/claude.ts +458 -0
  307. package/plugin/channel/runtime/codex-config.ts +77 -0
  308. package/plugin/channel/runtime/codex.ts +669 -0
  309. package/plugin/channel/runtime/command.ts +62 -0
  310. package/plugin/channel/runtime/factory.ts +15 -0
  311. package/plugin/channel/runtime/json-rpc.ts +154 -0
  312. package/plugin/channel/runtime/kimi.ts +277 -0
  313. package/plugin/channel/runtime/opencode.ts +248 -0
  314. package/plugin/channel/runtime/process.ts +84 -0
  315. package/plugin/channel/runtime/types.ts +45 -0
  316. package/plugin/channel/runtime-files.ts +73 -0
  317. package/plugin/channel/runtime-gateway.js +6396 -0
  318. package/plugin/channel/runtime-gateway.ts +358 -0
  319. package/plugin/channel/runtime-metrics.ts +52 -0
  320. package/plugin/channel/runtime-subscription.ts +180 -0
  321. package/plugin/channel/terminal-delivery.ts +131 -0
  322. package/plugin/channel/untrusted.ts +60 -0
  323. package/plugin/commands/committee.md +31 -0
  324. package/plugin/commands/exit.md +32 -0
  325. package/plugin/commands/goals.md +20 -0
  326. package/plugin/commands/join.md +18 -0
  327. package/plugin/commands/notice.md +20 -0
  328. package/plugin/commands/propose.md +19 -0
  329. package/plugin/commands/round.md +17 -0
  330. package/plugin/commands/session.md +22 -0
  331. package/plugin/commands/settings.md +25 -0
  332. package/plugin/commands/setup.md +21 -0
  333. package/plugin/commands/team.md +23 -0
  334. package/plugin/domains/custom.md +27 -0
  335. package/plugin/domains/finance.md +31 -0
  336. package/plugin/domains/healthcare.md +34 -0
  337. package/plugin/domains/legal.md +31 -0
  338. package/plugin/domains/marketing.md +33 -0
  339. package/plugin/domains/operations.md +31 -0
  340. package/plugin/domains/research.md +32 -0
  341. package/plugin/domains/sales.md +30 -0
  342. package/plugin/domains/software.md +37 -0
  343. package/plugin/hooks/activity.sh +6 -0
  344. package/plugin/hooks/digging.sh +68 -0
  345. package/plugin/hooks/growth.sh +42 -0
  346. package/plugin/hooks/hooks.json +106 -0
  347. package/plugin/hooks/notice-on-pr.sh +35 -0
  348. package/plugin/hooks/notice-on-stop.sh +80 -0
  349. package/plugin/hooks/notice-pending.sh +58 -0
  350. package/plugin/hooks/solo-en-ciudad.sh +23 -0
  351. package/plugin/hooks/tokens.sh +47 -0
  352. package/plugin/roles/examples/account-executive.md +25 -0
  353. package/plugin/roles/examples/ai-manager.md +34 -0
  354. package/plugin/roles/examples/associate.md +24 -0
  355. package/plugin/roles/examples/brand-lead.md +20 -0
  356. package/plugin/roles/examples/cfo.md +20 -0
  357. package/plugin/roles/examples/city-lead.md +25 -0
  358. package/plugin/roles/examples/clinical-director.md +26 -0
  359. package/plugin/roles/examples/clinical-ops.md +25 -0
  360. package/plugin/roles/examples/clinician.md +25 -0
  361. package/plugin/roles/examples/compliance.md +27 -0
  362. package/plugin/roles/examples/content.md +24 -0
  363. package/plugin/roles/examples/controller.md +27 -0
  364. package/plugin/roles/examples/cpto.md +41 -0
  365. package/plugin/roles/examples/customer-success.md +25 -0
  366. package/plugin/roles/examples/data-engineer.md +37 -0
  367. package/plugin/roles/examples/data.md +60 -0
  368. package/plugin/roles/examples/dev.md +37 -0
  369. package/plugin/roles/examples/devops.md +39 -0
  370. package/plugin/roles/examples/enablement.md +24 -0
  371. package/plugin/roles/examples/ethics.md +24 -0
  372. package/plugin/roles/examples/fin-analytics.md +27 -0
  373. package/plugin/roles/examples/health-compliance.md +25 -0
  374. package/plugin/roles/examples/health-data.md +26 -0
  375. package/plugin/roles/examples/knowledge.md +25 -0
  376. package/plugin/roles/examples/lifecycle.md +26 -0
  377. package/plugin/roles/examples/llm-engineer.md +34 -0
  378. package/plugin/roles/examples/managing-partner.md +20 -0
  379. package/plugin/roles/examples/methods.md +23 -0
  380. package/plugin/roles/examples/operations-lead.md +23 -0
  381. package/plugin/roles/examples/ops.md +24 -0
  382. package/plugin/roles/examples/patient-safety.md +26 -0
  383. package/plugin/roles/examples/performance.md +27 -0
  384. package/plugin/roles/examples/po.md +36 -0
  385. package/plugin/roles/examples/process-owner.md +23 -0
  386. package/plugin/roles/examples/product-design.md +39 -0
  387. package/plugin/roles/examples/program-manager.md +24 -0
  388. package/plugin/roles/examples/quality.md +25 -0
  389. package/plugin/roles/examples/research-director.md +24 -0
  390. package/plugin/roles/examples/research-ops.md +25 -0
  391. package/plugin/roles/examples/researcher.md +25 -0
  392. package/plugin/roles/examples/revenue-lead.md +25 -0
  393. package/plugin/roles/examples/revops.md +26 -0
  394. package/plugin/roles/examples/seo.md +27 -0
  395. package/plugin/roles/examples/specialist.md +24 -0
  396. package/plugin/scripts/admision.py +165 -0
  397. package/plugin/scripts/apaga.py +232 -0
  398. package/plugin/scripts/avatar.py +211 -0
  399. package/plugin/scripts/broker.py +531 -0
  400. package/plugin/scripts/cage.py +252 -0
  401. package/plugin/scripts/capabilities.py +197 -0
  402. package/plugin/scripts/card.py +312 -0
  403. package/plugin/scripts/cities.py +520 -0
  404. package/plugin/scripts/city-env.sh +90 -0
  405. package/plugin/scripts/city-runtime.sh +87 -0
  406. package/plugin/scripts/city-session.sh +526 -0
  407. package/plugin/scripts/city_env.py +67 -0
  408. package/plugin/scripts/crecimiento.py +140 -0
  409. package/plugin/scripts/deliberations.py +48 -0
  410. package/plugin/scripts/doctor.py +172 -0
  411. package/plugin/scripts/domains.py +201 -0
  412. package/plugin/scripts/evidencia.py +64 -0
  413. package/plugin/scripts/find-repos.sh +111 -0
  414. package/plugin/scripts/gh.py +159 -0
  415. package/plugin/scripts/hall_protocol.py +147 -0
  416. package/plugin/scripts/hook_activity.py +139 -0
  417. package/plugin/scripts/launch.py +94 -0
  418. package/plugin/scripts/logs.py +98 -0
  419. package/plugin/scripts/pairing.py +185 -0
  420. package/plugin/scripts/parcels.py +132 -0
  421. package/plugin/scripts/read-card.py +51 -0
  422. package/plugin/scripts/report.py +201 -0
  423. package/plugin/scripts/reset.py +180 -0
  424. package/plugin/scripts/roads.py +230 -0
  425. package/plugin/scripts/roles.py +274 -0
  426. package/plugin/scripts/runtime_log.py +69 -0
  427. package/plugin/scripts/runtime_processes.py +132 -0
  428. package/plugin/scripts/rutas.py +83 -0
  429. package/plugin/scripts/seat.py +1349 -0
  430. package/plugin/scripts/tokens.py +194 -0
  431. package/plugin/scripts/trust-repos.py +59 -0
  432. package/plugin/scripts/ui.py +280 -0
  433. package/plugin/scripts/units.py +80 -0
  434. package/plugin/scripts/workspace.py +364 -0
  435. package/plugin/skills/city/SKILL.md +191 -0
  436. package/templates/blank.md +39 -0
  437. package/templates/finance.md +44 -0
  438. package/templates/legal.md +45 -0
  439. package/templates/marketing.md +48 -0
  440. package/templates/product.md +45 -0
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env bash
2
+ # Configure the optional remote-road transport on this development machine.
3
+ # City identity is deliberately NOT written here: every running seat derives its
4
+ # own owner/city address from the selected city.
5
+
6
+ set -euo pipefail
7
+
8
+ BUS_URL="${CITY_BUS_URL:-https://bus.<your-subdomain>.workers.dev}"
9
+ CONF_DIR="$HOME/.claude/channels/city-bus"
10
+ REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)"
11
+ CHANNEL_DIR="$(cd "$REPO_DIR/../plugin/channel" && pwd)"
12
+
13
+ echo "── Configure remote city roads ─────────────────────────────────"
14
+ echo
15
+
16
+ if command -v bun >/dev/null 2>&1; then
17
+ RUNNER=bun
18
+ elif command -v node >/dev/null 2>&1; then
19
+ RUNNER=node
20
+ else
21
+ echo "I need node or bun on PATH." >&2
22
+ exit 1
23
+ fi
24
+
25
+ echo "→ installing the channel's development dependencies..."
26
+ (cd "$CHANNEL_DIR" && { command -v bun >/dev/null 2>&1 && bun install --silent || npm install --silent; })
27
+
28
+ read -rp "Owner prefix on the relay (for example alice): " OWNER
29
+ if ! [[ "$OWNER" =~ ^[a-z0-9][a-z0-9_-]{0,31}$ ]]; then
30
+ echo "Invalid owner: use lowercase letters, digits, dashes or underscores." >&2
31
+ exit 1
32
+ fi
33
+
34
+ read -rsp "Paste the token (starts with rb_; it will not be shown): " TOKEN; echo
35
+ if ! [[ "$TOKEN" =~ ^rb_[a-f0-9]{48}$ ]]; then
36
+ echo "That token does not look right (rb_ followed by 48 hex characters)." >&2
37
+ exit 1
38
+ fi
39
+
40
+ mkdir -p "$CONF_DIR"
41
+ umask 077
42
+ cat > "$CONF_DIR/.env" <<EOF
43
+ CITY_BUS_URL=$BUS_URL
44
+ CITY_BUS_TOKEN=$TOKEN
45
+ EOF
46
+ chmod 600 "$CONF_DIR/.env"
47
+ echo "✓ transport settings written without a global city address"
48
+
49
+ echo "→ checking authentication with the temporary address $OWNER/setup-check..."
50
+ RESULT="$(cd "$CHANNEL_DIR" && CITY_BUS_URL="$BUS_URL" CITY_BUS_TOKEN="$TOKEN" \
51
+ CITY_BUS_AGENT="$OWNER/setup-check" timeout 20 "$RUNNER" --eval '
52
+ const u = new URL("/ws", process.env.CITY_BUS_URL)
53
+ u.searchParams.set("agent", process.env.CITY_BUS_AGENT)
54
+ u.protocol = u.protocol.replace(/^http/, "ws")
55
+ const WS = globalThis.WebSocket || (await import("ws")).default
56
+ const ws = new WS(u.toString(), { headers: { Authorization: "Bearer " + process.env.CITY_BUS_TOKEN } })
57
+ ws.addEventListener("message", e => { const m = JSON.parse(String(e.data))
58
+ if (m.type === "welcome") { console.log("OK"); process.exit(0) } })
59
+ ws.addEventListener("close", e => { console.log("FAIL " + e.code + " " + e.reason); process.exit(1) })
60
+ setTimeout(() => { console.log("FAIL timeout"); process.exit(1) }, 15000)
61
+ ' 2>&1 | tail -1)"
62
+
63
+ if [ "$RESULT" = "OK" ]; then
64
+ echo "✓ the relay accepts this owner prefix"
65
+ else
66
+ echo "✗ I could not authenticate: $RESULT" >&2
67
+ exit 1
68
+ fi
69
+
70
+ cat <<'EOF'
71
+
72
+ Done. Open a road by exchanging invitations, then start each city seat:
73
+
74
+ agents-city road invite <city>
75
+ agents-city road connect <city> <other.invitation.json>
76
+ agents-city seat --city <city>
77
+
78
+ The seat derives owner/city itself. Do not add CITY_BUS_AGENT to the shared .env.
79
+ EOF
@@ -0,0 +1,8 @@
1
+ /** Compatibility entrypoint for the canonical local-channel integration test. */
2
+ import { spawnSync } from 'node:child_process';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const test = fileURLToPath(new URL('../../bin/test-channel.py', import.meta.url));
6
+ const result = spawnSync(test, { stdio: 'inherit' });
7
+
8
+ process.exit(result.status ?? 1);
@@ -0,0 +1,266 @@
1
+ import { request as httpRequest } from 'node:http';
2
+
3
+ const BASE = 'http://127.0.0.1:8799';
4
+ const ALICE = 'tok-alice-test';
5
+ const BOB = 'tok-bob-test';
6
+
7
+ function open(token: string, agent: string): Promise<{ ws: WebSocket; msgs: any[] }> {
8
+ return new Promise((resolve, reject) => {
9
+ const msgs: any[] = [];
10
+ const ws = new WebSocket(`${BASE.replace('http', 'ws')}/ws?agent=${agent}`, {
11
+ headers: { Authorization: `Bearer ${token}` },
12
+ } as any);
13
+ ws.addEventListener('message', (e) => {
14
+ try {
15
+ msgs.push(JSON.parse(String(e.data)));
16
+ } catch {}
17
+ });
18
+ ws.addEventListener('open', () => resolve({ ws, msgs }));
19
+ ws.addEventListener('error', () => reject(new Error('connection error')));
20
+ ws.addEventListener('close', (e) => reject(new Error(`closed ${e.code} ${e.reason}`)));
21
+ setTimeout(() => reject(new Error('timeout')), 5000);
22
+ });
23
+ }
24
+ const wait = (ms: number) => new Promise((r) => setTimeout(r, ms));
25
+ function rejected(path: string, token = ''): Promise<{ status: number; body: string }> {
26
+ return new Promise((resolve, reject) => {
27
+ const url = new URL(path, BASE);
28
+ const req = httpRequest(url, {
29
+ headers: {
30
+ Connection: 'Upgrade',
31
+ Upgrade: 'websocket',
32
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
33
+ },
34
+ });
35
+ req.on('response', (response) => {
36
+ let body = '';
37
+ response.setEncoding('utf8');
38
+ response.on('data', (chunk) => {
39
+ body += chunk;
40
+ });
41
+ response.on('end', () => resolve({ status: response.statusCode || 0, body }));
42
+ });
43
+ req.on('upgrade', (_response, socket) => {
44
+ socket.destroy();
45
+ resolve({ status: 101, body: '' });
46
+ });
47
+ req.on('error', reject);
48
+ req.end();
49
+ });
50
+ }
51
+ const roadEnvelope = (from: string, to: string, text: string) => ({
52
+ protocol: 'agents-city-bus/2',
53
+ id: `msg_${crypto.randomUUID()}`,
54
+ kind: 'road.message',
55
+ scope: 'road',
56
+ thread: null,
57
+ from: { city: from, actor: 'seat', role: 'chair' },
58
+ to: { city: to, actor: 'seat' },
59
+ createdAt: new Date().toISOString(),
60
+ payload: { text, trust: 'information-not-authority' },
61
+ });
62
+ let pass = 0,
63
+ fail = 0;
64
+ function check(name: string, ok: boolean, extra = '') {
65
+ console.log(`${ok ? '✅' : '❌'} ${name}${extra ? ' — ' + extra : ''}`);
66
+ ok ? pass++ : fail++;
67
+ }
68
+
69
+ // --- HTTP: rechazos ---
70
+ const noTok = await rejected('/ws');
71
+ check('no token -> 401', noTok.status === 401, `status ${noTok.status}`);
72
+
73
+ const badTok = await rejected('/ws?agent=alice/lead', 'made-up');
74
+ check('invalid token -> 403', badTok.status === 403, `status ${badTok.status}`);
75
+
76
+ const badName = await rejected('/ws?agent=MAYUS', ALICE);
77
+ check('invalid name -> 400', badName.status === 400, `status ${badName.status}`);
78
+
79
+ const imperson = await rejected('/ws?agent=bob/infra', ALICE);
80
+ check(
81
+ 'impersonating another user -> 403',
82
+ imperson.status === 403,
83
+ `status ${imperson.status} ${imperson.body.trim()}`,
84
+ );
85
+
86
+ // --- WS: flujo real ---
87
+ const a = await open(ALICE, 'alice/lead');
88
+ const b = await open(BOB, 'bob/infra');
89
+ await wait(400);
90
+
91
+ check(
92
+ 'a welcome on connecting',
93
+ a.msgs.some((m) => m.type === 'welcome'),
94
+ );
95
+ check(
96
+ 'the second agent shows up',
97
+ a.msgs.some((m) => m.type === 'presence' && m.agent === 'bob/infra' && m.status === 'online'),
98
+ );
99
+
100
+ // roster
101
+ b.ws.send(JSON.stringify({ type: 'roster' }));
102
+ await wait(300);
103
+ const ros = b.msgs.filter((m) => m.type === 'roster').pop();
104
+ check(
105
+ 'the roster sees the other one',
106
+ !!ros?.agents?.some((x: any) => x.agent === 'alice/lead'),
107
+ JSON.stringify(ros?.agents),
108
+ );
109
+
110
+ // mensaje dirigido
111
+ a.ws.send(JSON.stringify({ type: 'send', to: 'bob/infra', text: 'hello from the seat' }));
112
+ await wait(400);
113
+ const got = b.msgs.find((m) => m.type === 'msg');
114
+ check(
115
+ 'the message is delivered',
116
+ got?.text === 'hello from the seat' && got?.from === 'alice/lead',
117
+ JSON.stringify(got),
118
+ );
119
+ check(
120
+ 'the sender gets an acknowledgement',
121
+ a.msgs.some((m) => m.type === 'sent' && m.delivered === 1),
122
+ );
123
+
124
+ // The v2 local hub and this internet relay carry the same typed envelope.
125
+ const typed = roadEnvelope('alice/lead', 'bob/infra', 'typed over the road');
126
+ a.ws.send(
127
+ JSON.stringify({
128
+ type: 'send',
129
+ request_id: 'typed-1',
130
+ to: 'bob/infra',
131
+ text: 'typed over the road',
132
+ envelope: typed,
133
+ }),
134
+ );
135
+ await wait(400);
136
+ const typedGot = b.msgs.find((m) => m.type === 'msg' && m.envelope?.id === typed.id);
137
+ check(
138
+ 'the relay preserves the v2 envelope',
139
+ typedGot?.envelope?.protocol === 'agents-city-bus/2' &&
140
+ typedGot?.envelope?.scope === 'road' &&
141
+ typedGot?.envelope?.from?.actor === 'seat' &&
142
+ typedGot?.envelope?.from?.role === 'external-seat',
143
+ );
144
+ check(
145
+ 'typed acknowledgements correlate to their request',
146
+ a.msgs.some((m) => m.type === 'sent' && m.request_id === 'typed-1'),
147
+ );
148
+
149
+ const internal = { ...roadEnvelope('alice/lead', 'bob/infra', 'must fail'), scope: 'internal' };
150
+ a.ws.send(
151
+ JSON.stringify({
152
+ type: 'send',
153
+ request_id: 'typed-2',
154
+ to: 'bob/infra',
155
+ text: 'must fail',
156
+ envelope: internal,
157
+ }),
158
+ );
159
+ await wait(300);
160
+ check(
161
+ 'an internal committee envelope cannot cross the internet boundary',
162
+ a.msgs.some((m) => m.type === 'error' && m.request_id === 'typed-2' && /scope/.test(m.error)),
163
+ );
164
+
165
+ const spoofed = roadEnvelope('alice/other', 'bob/infra', 'must fail too');
166
+ a.ws.send(
167
+ JSON.stringify({
168
+ type: 'send',
169
+ request_id: 'typed-3',
170
+ to: 'bob/infra',
171
+ text: 'must fail too',
172
+ envelope: spoofed,
173
+ }),
174
+ );
175
+ await wait(300);
176
+ check(
177
+ 'the authenticated seat cannot spoof another city in an envelope',
178
+ a.msgs.some((m) => m.type === 'error' && m.request_id === 'typed-3' && /sender/.test(m.error)),
179
+ );
180
+
181
+ // respuesta en sentido contrario
182
+ b.ws.send(JSON.stringify({ type: 'send', to: 'alice/lead', text: 'recibido' }));
183
+ await wait(400);
184
+ check(
185
+ 'a reply travels the other way',
186
+ a.msgs.some((m) => m.type === 'msg' && m.text === 'recibido' && m.from === 'bob/infra'),
187
+ );
188
+
189
+ // destinatario inexistente: ahora se encola en vez de fallar
190
+ a.ws.send(JSON.stringify({ type: 'send', to: 'nadie/x', text: 'eco' }));
191
+ await wait(400);
192
+ check(
193
+ 'recipient offline -> queued',
194
+ a.msgs.some((m) => m.type === 'queued' && m.to === 'nadie/x'),
195
+ );
196
+
197
+ // a uno mismo
198
+ a.ws.send(JSON.stringify({ type: 'send', to: 'alice/lead', text: 'yo' }));
199
+ await wait(300);
200
+ check(
201
+ 'message to yourself -> error',
202
+ a.msgs.some((m) => m.type === 'error' && /that is you/.test(m.error)),
203
+ );
204
+
205
+ // broadcast
206
+ // A second agent of the SAME person: that is the normal case — a seat window
207
+ // and a repo window. The name here used to be left over from a find-and-replace
208
+ // and carried a space, which the hub rightly refuses, so this test never ran.
209
+ const c = await open(ALICE, 'alice/second');
210
+ await wait(300);
211
+ const bBefore = b.msgs.filter((m) => m.type === 'msg').length;
212
+ const cBefore = c.msgs.filter((m) => m.type === 'msg').length;
213
+ a.ws.send(JSON.stringify({ type: 'send', to: '*', text: 'to everybody' }));
214
+ await wait(500);
215
+ check(
216
+ 'a broadcast reaches everybody else',
217
+ b.msgs.filter((m) => m.type === 'msg').length === bBefore + 1 &&
218
+ c.msgs.filter((m) => m.type === 'msg').length === cBefore + 1,
219
+ );
220
+ check(
221
+ 'a broadcast does not come back to the sender',
222
+ !a.msgs.some((m) => m.type === 'msg' && m.text === 'to everybody'),
223
+ );
224
+
225
+ // size
226
+ a.ws.send(JSON.stringify({ type: 'send', to: 'bob/infra', text: 'x'.repeat(64_001) }));
227
+ await wait(400);
228
+ check(
229
+ 'an enormous message is refused',
230
+ a.msgs.some((m) => m.type === 'error' && /maximum/.test(m.error)),
231
+ );
232
+
233
+ // empty
234
+ a.ws.send(JSON.stringify({ type: 'send', to: 'bob/infra', text: ' ' }));
235
+ await wait(300);
236
+ check(
237
+ 'an empty message is refused',
238
+ a.msgs.some((m) => m.type === 'error' && /empty/.test(m.error)),
239
+ );
240
+
241
+ // the same agent reconnecting closes the previous session
242
+ let closed = 0;
243
+ b.ws.addEventListener('close', (e) => {
244
+ if (e.code === 4009) closed = 4009;
245
+ });
246
+ const b2 = await open(BOB, 'bob/infra');
247
+ await wait(600);
248
+ check('a reconnect closes the previous session', closed === 4009, `code=${closed}`);
249
+
250
+ // rate limit
251
+ const d = await open(BOB, 'bob/tmp'); // the token is bob's, so the name has to be too
252
+ await wait(200);
253
+ for (let i = 0; i < 35; i++)
254
+ d.ws.send(JSON.stringify({ type: 'send', to: 'alice/lead', text: `n${i}` }));
255
+ await wait(900);
256
+ check(
257
+ 'the rate limit fires',
258
+ d.msgs.some((m) => m.type === 'error' && /rate limit/.test(m.error)),
259
+ );
260
+
261
+ for (const s of [a.ws, b2.ws, c.ws, d.ws])
262
+ try {
263
+ s.close();
264
+ } catch {}
265
+ console.log(`\n${pass} ok, ${fail} failed`);
266
+ process.exit(fail ? 1 : 0);
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bash
2
+ # Exercise the internet-road Durable Object locally, including its durable queue.
3
+ set -euo pipefail
4
+
5
+ WORKER="$(cd "$(dirname "$0")/../worker" && pwd)"
6
+ STATE="$(mktemp -d /tmp/agents-city-bus.XXXXXX)"
7
+ LOG="$STATE/wrangler.log"
8
+ PID=""
9
+ cleanup() {
10
+ if [ -n "$PID" ]; then
11
+ kill "$PID" 2>/dev/null || true
12
+ wait "$PID" 2>/dev/null || true
13
+ fi
14
+ rm -rf "$STATE"
15
+ }
16
+ trap cleanup EXIT INT TERM
17
+
18
+ cd "$WORKER"
19
+ alice_hash="$(printf '%s' 'tok-alice-test' | shasum -a 256 | awk '{print $1}')"
20
+ bob_hash="$(printf '%s' 'tok-bob-test' | shasum -a 256 | awk '{print $1}')"
21
+ npm exec wrangler -- kv key put --local --persist-to "$STATE" --binding TOKENS "tok:$alice_hash" '{"user":"alice","can_send_to":["*"]}' >/dev/null
22
+ npm exec wrangler -- kv key put --local --persist-to "$STATE" --binding TOKENS "tok:$bob_hash" '{"user":"bob","can_send_to":["*"]}' >/dev/null
23
+ npm exec wrangler -- dev --local --persist-to "$STATE" --port 8799 --log-level error --show-interactive-dev-session=false >"$LOG" 2>&1 &
24
+ PID=$!
25
+
26
+ ready=0
27
+ for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do
28
+ if curl -fsS http://127.0.0.1:8799/health >/dev/null 2>&1; then
29
+ ready=1
30
+ break
31
+ fi
32
+ sleep .25
33
+ done
34
+ if [ "$ready" -ne 1 ]; then
35
+ cat "$LOG" >&2
36
+ exit 1
37
+ fi
38
+
39
+ NODE_NO_WARNINGS=1 node --experimental-strip-types ../scripts/test-hub.ts
40
+ NODE_NO_WARNINGS=1 node --experimental-strip-types ../scripts/test-queue.ts
@@ -0,0 +1,132 @@
1
+ const BASE = process.env.BUS || 'http://127.0.0.1:8799';
2
+ const ALICE = process.env.TJ || 'tok-alice-test';
3
+ const BOB = process.env.TA || 'tok-bob-test';
4
+ // The prefixes have to match the owners of those tokens.
5
+ const UA = process.env.UA || 'alice'; // owner of TA
6
+ const UB = process.env.UB || 'bob'; // owner of TB
7
+ function open(token: string, agent: string): Promise<{ ws: WebSocket; msgs: any[] }> {
8
+ return new Promise((res, rej) => {
9
+ const msgs: any[] = [];
10
+ const ws = new WebSocket(`${BASE.replace('http', 'ws')}/ws?agent=${agent}`, {
11
+ headers: { Authorization: `Bearer ${token}` },
12
+ } as any);
13
+ ws.addEventListener('message', (e) => {
14
+ try {
15
+ msgs.push(JSON.parse(String(e.data)));
16
+ } catch {}
17
+ });
18
+ ws.addEventListener('open', () => res({ ws, msgs }));
19
+ ws.addEventListener('close', (e) => rej(new Error('cerrado ' + e.code)));
20
+ setTimeout(() => rej(new Error('timeout')), 8000);
21
+ });
22
+ }
23
+ const wait = (m: number) => new Promise((r) => setTimeout(r, m));
24
+ let p = 0,
25
+ f = 0;
26
+ const ck = (n: string, ok: boolean, x = '') => {
27
+ console.log((ok ? '✅' : '❌') + ' ' + n + (x ? ' — ' + x : ''));
28
+ ok ? p++ : f++;
29
+ };
30
+
31
+ // 1. write to somebody who is NOT connected
32
+ const a = await open(ALICE, `${UA}/cpto`);
33
+ await wait(400);
34
+ a.ws.send(
35
+ JSON.stringify({ type: 'send', to: `${UB}/away`, text: 'revisa los CVE de las dependencias' }),
36
+ );
37
+ await wait(600);
38
+ const q = a.msgs.find((m: any) => m.type === 'queued');
39
+ ck('recipient away -> queued, not an error', !!q && q.to === `${UB}/away`, JSON.stringify(q));
40
+
41
+ // 2. un segundo mensaje para el mismo
42
+ a.ws.send(JSON.stringify({ type: 'send', to: `${UB}/away`, text: 'y el bundle size de la home' }));
43
+ await wait(600);
44
+ ck('a second message is queued too', a.msgs.filter((m: any) => m.type === 'queued').length === 2);
45
+
46
+ const typed = {
47
+ protocol: 'agents-city-bus/2',
48
+ id: `msg_${crypto.randomUUID()}`,
49
+ kind: 'road.message',
50
+ scope: 'road',
51
+ thread: null,
52
+ from: { city: `${UA}/cpto`, actor: 'seat', role: 'chair' },
53
+ to: { city: `${UB}/away`, actor: 'seat' },
54
+ createdAt: new Date().toISOString(),
55
+ payload: { text: 'typed and durable' },
56
+ };
57
+ a.ws.send(
58
+ JSON.stringify({
59
+ type: 'send',
60
+ to: `${UB}/away`,
61
+ text: 'typed and durable',
62
+ envelope: typed,
63
+ }),
64
+ );
65
+ await wait(600);
66
+ ck('a typed envelope is queued too', a.msgs.filter((m: any) => m.type === 'queued').length === 3);
67
+
68
+ // 3. el destinatario aparece y recibe lo pendiente, en orden
69
+ const b = await open(BOB, `${UB}/away`);
70
+ await wait(1200);
71
+ const got = b.msgs.filter((m: any) => m.type === 'msg');
72
+ ck('on connecting it gets all queued messages', got.length === 3, `received ${got.length}`);
73
+ ck(
74
+ 'they arrive in order',
75
+ got[0]?.text === 'revisa los CVE de las dependencias' &&
76
+ got[1]?.text === 'y el bundle size de la home',
77
+ );
78
+ ck(
79
+ 'marked as delayed',
80
+ typeof got[0]?.delayed_minutes === 'number',
81
+ `delayed_minutes=${got[0]?.delayed_minutes}`,
82
+ );
83
+ ck('they keep their sender', got[0]?.from === `${UA}/cpto`);
84
+ ck(
85
+ 'the typed envelope survives durable storage',
86
+ got[2]?.envelope?.id === typed.id && got[2]?.envelope?.from?.role === 'external-seat',
87
+ );
88
+
89
+ // 4. the queue drains: reconnecting does not duplicate them
90
+ b.ws.close();
91
+ await wait(600);
92
+ const b2 = await open(BOB, `${UB}/away`);
93
+ await wait(1000);
94
+ ck(
95
+ 'they are not delivered twice on reconnect',
96
+ b2.msgs.filter((m: any) => m.type === 'msg').length === 0,
97
+ `received ${b2.msgs.filter((m: any) => m.type === 'msg').length}`,
98
+ );
99
+
100
+ // 5. estando conectado, entrega directa (no cola)
101
+ a.ws.send(JSON.stringify({ type: 'send', to: `${UB}/away`, text: 'directo' }));
102
+ await wait(600);
103
+ ck(
104
+ 'with the peer connected it goes straight through',
105
+ a.msgs.some((m: any) => m.type === 'sent' && m.delivered === 1),
106
+ );
107
+ ck(
108
+ 'and arrives with no delay marker',
109
+ b2.msgs.some(
110
+ (m: any) => m.type === 'msg' && m.text === 'directo' && m.delayed_minutes === undefined,
111
+ ),
112
+ );
113
+
114
+ // 6. a broadcast with nobody else does not queue
115
+ try {
116
+ const solo = await open(ALICE, `${UA}/solo`);
117
+ await wait(400);
118
+ solo.ws.send(JSON.stringify({ type: 'send', to: '*', text: 'eco' }));
119
+ await wait(600);
120
+ ck(
121
+ 'a broadcast reaches whoever is connected',
122
+ solo.msgs.some((m: any) => m.type === 'sent'),
123
+ );
124
+ solo.ws.close();
125
+ } catch (e: any) {
126
+ ck('broadcast', false, e.message);
127
+ }
128
+
129
+ a.ws.close();
130
+ b2.ws.close();
131
+ console.log(`\n${p} ok, ${f} failed`);
132
+ process.exit(f ? 1 : 0);