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.
- package/.claude-plugin/marketplace.json +16 -0
- package/LICENSE +202 -0
- package/NOTICE +5 -0
- package/README.es.md +2021 -0
- package/README.md +1999 -0
- package/benchmarks/committee/README.md +39 -0
- package/benchmarks/committee/metrics.py +51 -0
- package/benchmarks/committee/run.py +64 -0
- package/benchmarks/committee/traces/01-single.json +8 -0
- package/benchmarks/committee/traces/02-mesh.json +16 -0
- package/benchmarks/committee/traces/03-chair.json +15 -0
- package/benchmarks/latency/README.md +35 -0
- package/benchmarks/latency/fake-claude-cli.mjs +119 -0
- package/benchmarks/latency/fake-native-server.mjs +340 -0
- package/benchmarks/latency/fake-tui.py +91 -0
- package/benchmarks/latency/live.py +380 -0
- package/benchmarks/stress/README.md +20 -0
- package/benchmarks/stress/run.py +487 -0
- package/bin/agents +4 -0
- package/bin/agents-city.js +116 -0
- package/bin/benchmark +17 -0
- package/bin/bus +6 -0
- package/bin/cities +3 -0
- package/bin/city +86 -0
- package/bin/committee +6 -0
- package/bin/demo +200 -0
- package/bin/exit +5 -0
- package/bin/hall +73 -0
- package/bin/hall.html +426 -0
- package/bin/logs +3 -0
- package/bin/report.py +14 -0
- package/bin/reset +3 -0
- package/bin/road +3 -0
- package/bin/seat +10 -0
- package/bin/serve.py +1428 -0
- package/bin/setup.py +753 -0
- package/bin/skills +3 -0
- package/bin/test +131 -0
- package/bin/test-adapter.py +183 -0
- package/bin/test-admision.py +99 -0
- package/bin/test-avatar.py +58 -0
- package/bin/test-benchmark.py +40 -0
- package/bin/test-broker.py +334 -0
- package/bin/test-cage.py +206 -0
- package/bin/test-card.py +402 -0
- package/bin/test-channel.py +379 -0
- package/bin/test-cities.py +453 -0
- package/bin/test-claude-runtime.py +252 -0
- package/bin/test-committee.py +340 -0
- package/bin/test-contracts.py +544 -0
- package/bin/test-crecimiento.py +164 -0
- package/bin/test-demo.py +308 -0
- package/bin/test-doctor.py +106 -0
- package/bin/test-domains.py +151 -0
- package/bin/test-evidencia.py +50 -0
- package/bin/test-exit.py +128 -0
- package/bin/test-hall-protocol.py +93 -0
- package/bin/test-launch.py +169 -0
- package/bin/test-live-feed.py +508 -0
- package/bin/test-pairing.py +99 -0
- package/bin/test-parcels.py +283 -0
- package/bin/test-runtime-failures.py +252 -0
- package/bin/test-runtime-ui.py +488 -0
- package/bin/test-runtime.py +268 -0
- package/bin/test-rutas.py +80 -0
- package/bin/test-seat.py +1386 -0
- package/bin/test-security.py +150 -0
- package/bin/test-serve.py +1057 -0
- package/bin/test-stress.py +64 -0
- package/bin/test-widgets.py +143 -0
- package/bin/test-workspace.py +188 -0
- package/bin/testlib.py +95 -0
- package/bin/tokens.py +14 -0
- package/bus/scripts/deploy.sh +60 -0
- package/bus/scripts/mint-token.sh +68 -0
- package/bus/scripts/setup-dev.sh +79 -0
- package/bus/scripts/test-channel.ts +8 -0
- package/bus/scripts/test-hub.ts +266 -0
- package/bus/scripts/test-local.sh +40 -0
- package/bus/scripts/test-queue.ts +132 -0
- package/bus/worker/package-lock.json +1971 -0
- package/bus/worker/package.json +17 -0
- package/bus/worker/src/index.ts +485 -0
- package/bus/worker/tsconfig.json +13 -0
- package/bus/worker/wrangler.toml +32 -0
- package/city/oven/README.md +59 -0
- package/city/oven/collect.py +43 -0
- package/city/oven/oven.html +4 -0
- package/city/scripts/history.py +120 -0
- package/city/scripts/seed.py +442 -0
- package/city/web/assets/modelos/License.txt +28 -0
- package/city/web/assets/modelos/PROCEDENCIA.txt +3 -0
- package/city/web/assets/modelos/building-a.glb +0 -0
- package/city/web/assets/modelos/building-b.glb +0 -0
- package/city/web/assets/modelos/building-c.glb +0 -0
- package/city/web/assets/modelos/building-d.glb +0 -0
- package/city/web/assets/modelos/building-e.glb +0 -0
- package/city/web/assets/modelos/building-f.glb +0 -0
- package/city/web/assets/modelos/building-g.glb +0 -0
- package/city/web/assets/modelos/building-h.glb +0 -0
- package/city/web/assets/sprites/building-a.png +0 -0
- package/city/web/assets/sprites/building-b.png +0 -0
- package/city/web/assets/sprites/building-c.png +0 -0
- package/city/web/assets/sprites/building-d.png +0 -0
- package/city/web/assets/sprites/building-e.png +0 -0
- package/city/web/assets/sprites/building-f.png +0 -0
- package/city/web/assets/sprites/building-g.png +0 -0
- package/city/web/assets/sprites/building-h.png +0 -0
- package/city/web/assets/sprites/building-i.png +0 -0
- package/city/web/assets/sprites/building-j.png +0 -0
- package/city/web/assets/sprites/building-k.png +0 -0
- package/city/web/assets/sprites/building-l.png +0 -0
- package/city/web/assets/sprites/building-m.png +0 -0
- package/city/web/assets/sprites/building-n.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-a.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-b.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-c.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-d.png +0 -0
- package/city/web/assets/sprites/building-skyscraper-e.png +0 -0
- package/city/web/assets/sprites/catalogo.json +47 -0
- package/city/web/assets/sprites/detail-awning-wide.png +0 -0
- package/city/web/assets/sprites/detail-awning.png +0 -0
- package/city/web/assets/sprites/detail-overhang-wide.png +0 -0
- package/city/web/assets/sprites/detail-overhang.png +0 -0
- package/city/web/assets/sprites/detail-parasol-a.png +0 -0
- package/city/web/assets/sprites/detail-parasol-b.png +0 -0
- package/city/web/assets/sprites/low-detail-building-a.png +0 -0
- package/city/web/assets/sprites/low-detail-building-b.png +0 -0
- package/city/web/assets/sprites/low-detail-building-c.png +0 -0
- package/city/web/assets/sprites/low-detail-building-d.png +0 -0
- package/city/web/assets/sprites/low-detail-building-e.png +0 -0
- package/city/web/assets/sprites/low-detail-building-f.png +0 -0
- package/city/web/assets/sprites/low-detail-building-g.png +0 -0
- package/city/web/assets/sprites/low-detail-building-h.png +0 -0
- package/city/web/assets/sprites/low-detail-building-i.png +0 -0
- package/city/web/assets/sprites/low-detail-building-j.png +0 -0
- package/city/web/assets/sprites/low-detail-building-k.png +0 -0
- package/city/web/assets/sprites/low-detail-building-l.png +0 -0
- package/city/web/assets/sprites/low-detail-building-m.png +0 -0
- package/city/web/assets/sprites/low-detail-building-n.png +0 -0
- package/city/web/assets/sprites/low-detail-building-wide-a.png +0 -0
- package/city/web/assets/sprites/low-detail-building-wide-b.png +0 -0
- package/city/web/assets/sprites/medidas.json +248 -0
- package/city/web/dist/city.js +939 -0
- package/city/web/dist/index.html +294 -0
- package/city/web/dist/modelos/License.txt +28 -0
- package/city/web/dist/modelos/PROCEDENCIA.txt +3 -0
- package/city/web/dist/modelos/building-a.glb +0 -0
- package/city/web/dist/modelos/building-b.glb +0 -0
- package/city/web/dist/modelos/building-c.glb +0 -0
- package/city/web/dist/modelos/building-d.glb +0 -0
- package/city/web/dist/modelos/building-e.glb +0 -0
- package/city/web/dist/modelos/building-f.glb +0 -0
- package/city/web/dist/modelos/building-g.glb +0 -0
- package/city/web/dist/modelos/building-h.glb +0 -0
- package/city/web/dist/sprites/building-a.png +0 -0
- package/city/web/dist/sprites/building-b.png +0 -0
- package/city/web/dist/sprites/building-c.png +0 -0
- package/city/web/dist/sprites/building-d.png +0 -0
- package/city/web/dist/sprites/building-e.png +0 -0
- package/city/web/dist/sprites/building-f.png +0 -0
- package/city/web/dist/sprites/building-g.png +0 -0
- package/city/web/dist/sprites/building-h.png +0 -0
- package/city/web/dist/sprites/building-i.png +0 -0
- package/city/web/dist/sprites/building-j.png +0 -0
- package/city/web/dist/sprites/building-k.png +0 -0
- package/city/web/dist/sprites/building-l.png +0 -0
- package/city/web/dist/sprites/building-m.png +0 -0
- package/city/web/dist/sprites/building-n.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-a.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-b.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-c.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-d.png +0 -0
- package/city/web/dist/sprites/building-skyscraper-e.png +0 -0
- package/city/web/dist/sprites/catalogo.json +47 -0
- package/city/web/dist/sprites/detail-awning-wide.png +0 -0
- package/city/web/dist/sprites/detail-awning.png +0 -0
- package/city/web/dist/sprites/detail-overhang-wide.png +0 -0
- package/city/web/dist/sprites/detail-overhang.png +0 -0
- package/city/web/dist/sprites/detail-parasol-a.png +0 -0
- package/city/web/dist/sprites/detail-parasol-b.png +0 -0
- package/city/web/dist/sprites/low-detail-building-a.png +0 -0
- package/city/web/dist/sprites/low-detail-building-b.png +0 -0
- package/city/web/dist/sprites/low-detail-building-c.png +0 -0
- package/city/web/dist/sprites/low-detail-building-d.png +0 -0
- package/city/web/dist/sprites/low-detail-building-e.png +0 -0
- package/city/web/dist/sprites/low-detail-building-f.png +0 -0
- package/city/web/dist/sprites/low-detail-building-g.png +0 -0
- package/city/web/dist/sprites/low-detail-building-h.png +0 -0
- package/city/web/dist/sprites/low-detail-building-i.png +0 -0
- package/city/web/dist/sprites/low-detail-building-j.png +0 -0
- package/city/web/dist/sprites/low-detail-building-k.png +0 -0
- package/city/web/dist/sprites/low-detail-building-l.png +0 -0
- package/city/web/dist/sprites/low-detail-building-m.png +0 -0
- package/city/web/dist/sprites/low-detail-building-n.png +0 -0
- package/city/web/dist/sprites/low-detail-building-wide-a.png +0 -0
- package/city/web/dist/sprites/low-detail-building-wide-b.png +0 -0
- package/city/web/dist/sprites/medidas.json +248 -0
- package/city/web/dist-hall/hall.js +264 -0
- package/city/web/index.html +294 -0
- package/city/web/package.json +18 -0
- package/city/web/sella.py +21 -0
- package/city/web/src/activity-actors.ts +47 -0
- package/city/web/src/activity.ts +172 -0
- package/city/web/src/ayuntamiento.ts +580 -0
- package/city/web/src/draw.ts +640 -0
- package/city/web/src/game-speech.ts +79 -0
- package/city/web/src/hall.ts +1867 -0
- package/city/web/src/main.ts +2225 -0
- package/city/web/src/oven.ts +124 -0
- package/city/web/src/people.ts +244 -0
- package/city/web/src/presencia.ts +193 -0
- package/city/web/src/puertas.ts +180 -0
- package/city/web/src/repo-roles.ts +59 -0
- package/city/worker/package.json +16 -0
- package/city/worker/schema.sql +117 -0
- package/city/worker/src/index.ts +702 -0
- package/city/worker/src/square.ts +218 -0
- package/city/worker/wrangler.toml +58 -0
- package/demo/ada.md +50 -0
- package/demo/bruno.md +62 -0
- package/demo/camila.md +56 -0
- package/demo/city/ada.md +40 -0
- package/demo/city/city.yml +8 -0
- package/demo/city/parcels.yml +15 -0
- package/demo/city/roads.json +4 -0
- package/demo/city/units.yml +10 -0
- package/demo/clinica/city.yml +8 -0
- package/demo/clinica/parcels.yml +15 -0
- package/demo/clinica/roads.json +4 -0
- package/demo/clinica/units.yml +9 -0
- package/demo/clinica/vera.md +46 -0
- package/demo/dante.md +54 -0
- package/demo/despacho/city.yml +8 -0
- package/demo/despacho/marta.md +46 -0
- package/demo/despacho/parcels.yml +15 -0
- package/demo/despacho/roads.json +4 -0
- package/demo/despacho/units.yml +9 -0
- package/demo/elsa.md +50 -0
- package/demo/farid.md +52 -0
- package/demo/greta.md +50 -0
- package/demo/hugo.md +49 -0
- package/demo/iris.md +49 -0
- package/demo/jonas.md +51 -0
- package/demo/kira.md +51 -0
- package/demo/luca.md +49 -0
- package/demo/parcels.yml +92 -0
- package/demo/seed.py +20 -0
- package/demo/show.py +161 -0
- package/demo/stories.py +824 -0
- package/demo/units.yml +33 -0
- package/docs/agents-first.md +99 -0
- package/docs/glossary.md +55 -0
- package/docs/map-live-layers.md +73 -0
- package/docs/security.md +231 -0
- package/docs/self-host.md +151 -0
- package/docs/testing.md +205 -0
- package/package.json +73 -0
- package/plugin/.claude-plugin/plugin.json +62 -0
- package/plugin/.mcp.json +15 -0
- package/plugin/channel/activity-cli.ts +16 -0
- package/plugin/channel/adapter-prompts.ts +113 -0
- package/plugin/channel/adapter.js +4292 -0
- package/plugin/channel/adapter.ts +133 -0
- package/plugin/channel/bus.js +20188 -0
- package/plugin/channel/bus.ts +131 -0
- package/plugin/channel/city-config.ts +125 -0
- package/plugin/channel/claude-channel.ts +58 -0
- package/plugin/channel/cli-args.ts +75 -0
- package/plugin/channel/client.js +4200 -0
- package/plugin/channel/client.ts +38 -0
- package/plugin/channel/committee/activity.ts +256 -0
- package/plugin/channel/committee/collection.ts +154 -0
- package/plugin/channel/committee/decision.ts +175 -0
- package/plugin/channel/committee/floor.ts +135 -0
- package/plugin/channel/committee/guards.ts +30 -0
- package/plugin/channel/committee/history.ts +38 -0
- package/plugin/channel/committee/render.ts +102 -0
- package/plugin/channel/committee/service.ts +72 -0
- package/plugin/channel/committee/storage.ts +92 -0
- package/plugin/channel/committee/types.ts +163 -0
- package/plugin/channel/committee/view.ts +63 -0
- package/plugin/channel/committee-cli.ts +109 -0
- package/plugin/channel/delivery-metrics.ts +61 -0
- package/plugin/channel/delivery-queue.ts +118 -0
- package/plugin/channel/hub/activity-controller.ts +72 -0
- package/plugin/channel/hub/activity-feed.ts +169 -0
- package/plugin/channel/hub/committee-controller.ts +60 -0
- package/plugin/channel/hub/connections.ts +48 -0
- package/plugin/channel/hub/diagnostics.ts +67 -0
- package/plugin/channel/hub/envelope-validity.ts +109 -0
- package/plugin/channel/hub/envelopes.ts +66 -0
- package/plugin/channel/hub/lifecycle.ts +91 -0
- package/plugin/channel/hub/local-roads.ts +89 -0
- package/plugin/channel/hub/remote-roads.ts +165 -0
- package/plugin/channel/hub/road-controller.ts +99 -0
- package/plugin/channel/hub-client.ts +149 -0
- package/plugin/channel/local-hub.js +5995 -0
- package/plugin/channel/local-hub.ts +349 -0
- package/plugin/channel/map-reporter.ts +48 -0
- package/plugin/channel/package-lock.json +1731 -0
- package/plugin/channel/package.json +19 -0
- package/plugin/channel/protocol.ts +98 -0
- package/plugin/channel/road-cli.ts +18 -0
- package/plugin/channel/run.sh +15 -0
- package/plugin/channel/runtime/claude.ts +458 -0
- package/plugin/channel/runtime/codex-config.ts +77 -0
- package/plugin/channel/runtime/codex.ts +669 -0
- package/plugin/channel/runtime/command.ts +62 -0
- package/plugin/channel/runtime/factory.ts +15 -0
- package/plugin/channel/runtime/json-rpc.ts +154 -0
- package/plugin/channel/runtime/kimi.ts +277 -0
- package/plugin/channel/runtime/opencode.ts +248 -0
- package/plugin/channel/runtime/process.ts +84 -0
- package/plugin/channel/runtime/types.ts +45 -0
- package/plugin/channel/runtime-files.ts +73 -0
- package/plugin/channel/runtime-gateway.js +6396 -0
- package/plugin/channel/runtime-gateway.ts +358 -0
- package/plugin/channel/runtime-metrics.ts +52 -0
- package/plugin/channel/runtime-subscription.ts +180 -0
- package/plugin/channel/terminal-delivery.ts +131 -0
- package/plugin/channel/untrusted.ts +60 -0
- package/plugin/commands/committee.md +31 -0
- package/plugin/commands/exit.md +32 -0
- package/plugin/commands/goals.md +20 -0
- package/plugin/commands/join.md +18 -0
- package/plugin/commands/notice.md +20 -0
- package/plugin/commands/propose.md +19 -0
- package/plugin/commands/round.md +17 -0
- package/plugin/commands/session.md +22 -0
- package/plugin/commands/settings.md +25 -0
- package/plugin/commands/setup.md +21 -0
- package/plugin/commands/team.md +23 -0
- package/plugin/domains/custom.md +27 -0
- package/plugin/domains/finance.md +31 -0
- package/plugin/domains/healthcare.md +34 -0
- package/plugin/domains/legal.md +31 -0
- package/plugin/domains/marketing.md +33 -0
- package/plugin/domains/operations.md +31 -0
- package/plugin/domains/research.md +32 -0
- package/plugin/domains/sales.md +30 -0
- package/plugin/domains/software.md +37 -0
- package/plugin/hooks/activity.sh +6 -0
- package/plugin/hooks/digging.sh +68 -0
- package/plugin/hooks/growth.sh +42 -0
- package/plugin/hooks/hooks.json +106 -0
- package/plugin/hooks/notice-on-pr.sh +35 -0
- package/plugin/hooks/notice-on-stop.sh +80 -0
- package/plugin/hooks/notice-pending.sh +58 -0
- package/plugin/hooks/solo-en-ciudad.sh +23 -0
- package/plugin/hooks/tokens.sh +47 -0
- package/plugin/roles/examples/account-executive.md +25 -0
- package/plugin/roles/examples/ai-manager.md +34 -0
- package/plugin/roles/examples/associate.md +24 -0
- package/plugin/roles/examples/brand-lead.md +20 -0
- package/plugin/roles/examples/cfo.md +20 -0
- package/plugin/roles/examples/city-lead.md +25 -0
- package/plugin/roles/examples/clinical-director.md +26 -0
- package/plugin/roles/examples/clinical-ops.md +25 -0
- package/plugin/roles/examples/clinician.md +25 -0
- package/plugin/roles/examples/compliance.md +27 -0
- package/plugin/roles/examples/content.md +24 -0
- package/plugin/roles/examples/controller.md +27 -0
- package/plugin/roles/examples/cpto.md +41 -0
- package/plugin/roles/examples/customer-success.md +25 -0
- package/plugin/roles/examples/data-engineer.md +37 -0
- package/plugin/roles/examples/data.md +60 -0
- package/plugin/roles/examples/dev.md +37 -0
- package/plugin/roles/examples/devops.md +39 -0
- package/plugin/roles/examples/enablement.md +24 -0
- package/plugin/roles/examples/ethics.md +24 -0
- package/plugin/roles/examples/fin-analytics.md +27 -0
- package/plugin/roles/examples/health-compliance.md +25 -0
- package/plugin/roles/examples/health-data.md +26 -0
- package/plugin/roles/examples/knowledge.md +25 -0
- package/plugin/roles/examples/lifecycle.md +26 -0
- package/plugin/roles/examples/llm-engineer.md +34 -0
- package/plugin/roles/examples/managing-partner.md +20 -0
- package/plugin/roles/examples/methods.md +23 -0
- package/plugin/roles/examples/operations-lead.md +23 -0
- package/plugin/roles/examples/ops.md +24 -0
- package/plugin/roles/examples/patient-safety.md +26 -0
- package/plugin/roles/examples/performance.md +27 -0
- package/plugin/roles/examples/po.md +36 -0
- package/plugin/roles/examples/process-owner.md +23 -0
- package/plugin/roles/examples/product-design.md +39 -0
- package/plugin/roles/examples/program-manager.md +24 -0
- package/plugin/roles/examples/quality.md +25 -0
- package/plugin/roles/examples/research-director.md +24 -0
- package/plugin/roles/examples/research-ops.md +25 -0
- package/plugin/roles/examples/researcher.md +25 -0
- package/plugin/roles/examples/revenue-lead.md +25 -0
- package/plugin/roles/examples/revops.md +26 -0
- package/plugin/roles/examples/seo.md +27 -0
- package/plugin/roles/examples/specialist.md +24 -0
- package/plugin/scripts/admision.py +165 -0
- package/plugin/scripts/apaga.py +232 -0
- package/plugin/scripts/avatar.py +211 -0
- package/plugin/scripts/broker.py +531 -0
- package/plugin/scripts/cage.py +252 -0
- package/plugin/scripts/capabilities.py +197 -0
- package/plugin/scripts/card.py +312 -0
- package/plugin/scripts/cities.py +520 -0
- package/plugin/scripts/city-env.sh +90 -0
- package/plugin/scripts/city-runtime.sh +87 -0
- package/plugin/scripts/city-session.sh +526 -0
- package/plugin/scripts/city_env.py +67 -0
- package/plugin/scripts/crecimiento.py +140 -0
- package/plugin/scripts/deliberations.py +48 -0
- package/plugin/scripts/doctor.py +172 -0
- package/plugin/scripts/domains.py +201 -0
- package/plugin/scripts/evidencia.py +64 -0
- package/plugin/scripts/find-repos.sh +111 -0
- package/plugin/scripts/gh.py +159 -0
- package/plugin/scripts/hall_protocol.py +147 -0
- package/plugin/scripts/hook_activity.py +139 -0
- package/plugin/scripts/launch.py +94 -0
- package/plugin/scripts/logs.py +98 -0
- package/plugin/scripts/pairing.py +185 -0
- package/plugin/scripts/parcels.py +132 -0
- package/plugin/scripts/read-card.py +51 -0
- package/plugin/scripts/report.py +201 -0
- package/plugin/scripts/reset.py +180 -0
- package/plugin/scripts/roads.py +230 -0
- package/plugin/scripts/roles.py +274 -0
- package/plugin/scripts/runtime_log.py +69 -0
- package/plugin/scripts/runtime_processes.py +132 -0
- package/plugin/scripts/rutas.py +83 -0
- package/plugin/scripts/seat.py +1349 -0
- package/plugin/scripts/tokens.py +194 -0
- package/plugin/scripts/trust-repos.py +59 -0
- package/plugin/scripts/ui.py +280 -0
- package/plugin/scripts/units.py +80 -0
- package/plugin/scripts/workspace.py +364 -0
- package/plugin/skills/city/SKILL.md +191 -0
- package/templates/blank.md +39 -0
- package/templates/finance.md +44 -0
- package/templates/legal.md +45 -0
- package/templates/marketing.md +48 -0
- package/templates/product.md +45 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="es">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Agents City</title>
|
|
7
|
+
<script>if(new URLSearchParams(location.search).get('embed')==='1')document.documentElement.classList.add('embed')</script>
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Chakra+Petch:wght@500;600;700&family=Newsreader:opsz,wght@6..72,400;6..72,500&family=IBM+Plex+Mono:wght@400;500&display=swap">
|
|
11
|
+
<style>
|
|
12
|
+
:root{
|
|
13
|
+
--suelo:#131a24; --suelo2:#1b2431; --suelo3:#222d3c; --linea:#26313f; --linea2:#38495f;
|
|
14
|
+
--tinta:#e8e2d4; --tinta2:#9aa7b8; --tinta3:#6b7889;
|
|
15
|
+
--nucleo:#c8b48a; /* los colores de cada unidad llegan de la API */
|
|
16
|
+
--lampara:#f5c451; --carta:#e2604f;
|
|
17
|
+
--hud:"Chakra Petch",system-ui,sans-serif; --texto:"Newsreader",Georgia,serif;
|
|
18
|
+
--mono:"IBM Plex Mono",ui-monospace,monospace;
|
|
19
|
+
}
|
|
20
|
+
*{box-sizing:border-box}
|
|
21
|
+
html,body{height:100%}
|
|
22
|
+
body{margin:0;background:var(--suelo);color:var(--tinta);font-family:var(--texto);overflow:hidden}
|
|
23
|
+
h1,h2{font-family:var(--hud);margin:0}
|
|
24
|
+
#app{display:grid;grid-template-columns:minmax(230px,280px) minmax(320px,1fr) minmax(290px,350px);
|
|
25
|
+
height:100%;min-width:840px}
|
|
26
|
+
@media(max-width:1180px){#app{grid-template-columns:230px minmax(320px,1fr) 290px}}
|
|
27
|
+
html.embed #app{grid-template-columns:minmax(0,1fr);min-width:0}
|
|
28
|
+
html.embed aside,html.embed #avisos{display:none}
|
|
29
|
+
|
|
30
|
+
/* Persistent information belongs in the sidebars. The middle is the city, not
|
|
31
|
+
a dashboard with panels floating above and below it. */
|
|
32
|
+
#avisos{background:var(--panel);border-left:1px solid var(--linea);
|
|
33
|
+
border-right:0;display:flex;flex-direction:column;min-height:0;overflow-y:auto}
|
|
34
|
+
#avisos header{padding:16px 16px 10px;border-bottom:1px solid var(--linea)}
|
|
35
|
+
#avisos h2{display:flex;align-items:baseline;gap:8px}
|
|
36
|
+
#avisos h2 b{font-family:var(--mono);font-size:11px;font-weight:500;color:var(--carta);
|
|
37
|
+
letter-spacing:0;text-transform:none}
|
|
38
|
+
#avisos header p{margin:6px 0 0;font-size:11.5px;line-height:1.45;color:var(--tinta3)}
|
|
39
|
+
#avisos #tt{padding:10px 14px 16px;gap:0;display:block}
|
|
40
|
+
#avisos .vacio2{margin:0;padding:0 16px 18px;font-size:12.5px;color:var(--tinta3);font-style:italic}
|
|
41
|
+
aside{background:var(--suelo2);border-right:1px solid var(--linea);padding:20px 18px;
|
|
42
|
+
display:flex;flex-direction:column;gap:18px;overflow-y:auto}
|
|
43
|
+
.badge{display:inline-flex;align-items:center;gap:7px;align-self:flex-start;font-family:var(--hud);
|
|
44
|
+
font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--lampara);
|
|
45
|
+
border:1px solid #4a4127;background:#241f14;padding:4px 9px;border-radius:2px}
|
|
46
|
+
.badge::before{content:"";width:5px;height:5px;border-radius:50%;background:var(--lampara)}
|
|
47
|
+
h1{font-size:23px;line-height:1.05;margin:8px 0 5px}
|
|
48
|
+
p.sub{font-size:13px;line-height:1.5;color:var(--tinta2);margin:0}
|
|
49
|
+
.cifras{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--linea)}
|
|
50
|
+
.cifra{background:var(--suelo);padding:10px 9px}
|
|
51
|
+
.cifra b{display:block;font-family:var(--mono);font-size:20px;font-weight:500;font-variant-numeric:tabular-nums}
|
|
52
|
+
.cifra span{font-family:var(--hud);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--tinta3)}
|
|
53
|
+
.cifra.viva b{color:var(--lampara)} .cifra.aviso b{color:var(--carta)}
|
|
54
|
+
.gasto{background:var(--suelo);border:1px solid var(--linea);border-top:none;padding:11px}
|
|
55
|
+
.gasto .gt b{display:block;font-family:var(--mono);font-size:26px;font-weight:500;
|
|
56
|
+
font-variant-numeric:tabular-nums;letter-spacing:-.02em;color:var(--tinta)}
|
|
57
|
+
.gasto .gt span{font-family:var(--hud);font-size:9px;letter-spacing:.1em;
|
|
58
|
+
text-transform:uppercase;color:var(--tinta3)}
|
|
59
|
+
.gasto .gm{margin-top:7px;font-family:var(--mono);font-size:11px;color:var(--tinta2)}
|
|
60
|
+
.gasto .gn{margin:7px 0 0;font-size:11px;line-height:1.45;color:var(--tinta3);font-style:italic}
|
|
61
|
+
h2{font-size:10px;letter-spacing:.15em;text-transform:uppercase;color:var(--tinta3);margin-bottom:8px}
|
|
62
|
+
.leyenda{display:flex;flex-direction:column;gap:5px;font-size:12.5px;color:var(--tinta2)}
|
|
63
|
+
.leyenda div{display:flex;align-items:center;gap:8px}
|
|
64
|
+
.nota2{color:var(--tinta3);font-size:11px;font-style:italic}
|
|
65
|
+
.pip{width:9px;height:9px;border-radius:1px;flex:0 0 9px}
|
|
66
|
+
#cuadrillas{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:9px}
|
|
67
|
+
#cuadrillas .quien{display:flex;align-items:center;gap:7px;font-family:var(--hud);font-size:12.5px;color:var(--tinta)}
|
|
68
|
+
#cuadrillas .of{font-family:var(--texto);font-size:11px;color:var(--tinta3);font-style:italic}
|
|
69
|
+
#cuadrillas .cuantos{margin-left:auto;font-family:var(--mono);font-size:10px;color:var(--tinta3)}
|
|
70
|
+
#cuadrillas .ag{display:flex;gap:8px;margin:3px 0 0 16px;font-family:var(--mono);font-size:11px}
|
|
71
|
+
#cuadrillas .an{color:var(--tinta2)}
|
|
72
|
+
#cuadrillas .do{color:var(--tinta3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
73
|
+
#cuadrillas .vacio{color:var(--tinta3);font-size:12.5px;font-family:var(--texto)}
|
|
74
|
+
#tt{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:6px;font-size:12.5px}
|
|
75
|
+
#tt li{display:block;padding:9px 0;border-bottom:1px solid var(--linea);line-height:1.45}
|
|
76
|
+
#tt li:last-child{border-bottom:none}
|
|
77
|
+
#tt .cab{display:flex;gap:7px;align-items:center;margin-bottom:3px}
|
|
78
|
+
#tt .cuando{margin-left:auto;font-family:var(--mono);font-size:10px;color:var(--tinta3)}
|
|
79
|
+
#tt .cuerpo{font-size:12.5px;color:var(--tinta2)}
|
|
80
|
+
#clave{border-top:1px solid var(--linea);padding-top:10px}
|
|
81
|
+
#clave summary{font-family:var(--hud);font-size:10px;letter-spacing:.15em;
|
|
82
|
+
text-transform:uppercase;color:var(--tinta3);cursor:pointer;list-style:none}
|
|
83
|
+
#clave summary::marker,#clave summary::-webkit-details-marker{display:none}
|
|
84
|
+
#clave summary::before{content:"+ ";color:var(--tinta3)}
|
|
85
|
+
#clave[open] summary::before{content:"− "}
|
|
86
|
+
#clave summary:hover{color:var(--tinta2)}
|
|
87
|
+
#clave summary:focus-visible{outline:1px solid var(--lampara);outline-offset:3px}
|
|
88
|
+
#clave dl{margin:9px 0 0;display:grid;grid-template-columns:auto 1fr;gap:4px 10px;font-size:12px}
|
|
89
|
+
#clave dt{font-family:var(--hud);font-size:9px;letter-spacing:.08em;text-transform:uppercase;
|
|
90
|
+
color:var(--tinta3);padding-top:2px;white-space:nowrap}
|
|
91
|
+
#clave dd{margin:0;color:var(--tinta2);line-height:1.35}
|
|
92
|
+
#clave p{margin:9px 0 0;font-size:11px;font-style:italic;color:var(--tinta3);line-height:1.4}
|
|
93
|
+
.etq{font-family:var(--hud);font-size:9px;letter-spacing:.1em;text-transform:uppercase;
|
|
94
|
+
border:1px solid currentColor;padding:1px 5px;border-radius:2px;flex:0 0 auto}
|
|
95
|
+
.de{font-family:var(--mono);font-size:11px;color:var(--tinta3)}
|
|
96
|
+
.estado{margin-top:auto;font-size:11.5px;color:var(--tinta3);font-family:var(--mono);
|
|
97
|
+
border-top:1px solid var(--linea);padding-top:12px;display:flex;align-items:center;gap:7px}
|
|
98
|
+
.punto{width:6px;height:6px;border-radius:50%;background:var(--carta)}
|
|
99
|
+
.punto.on{background:#4ec98a}
|
|
100
|
+
main{position:relative;min-width:0;min-height:0;overflow:hidden}
|
|
101
|
+
canvas{display:block}
|
|
102
|
+
.gameSpeech{--speech:#f5c451;position:absolute;z-index:8;width:min(244px,calc(100% - 24px));
|
|
103
|
+
transform:translate(-50%,calc(-100% - 13px));padding:9px 11px 10px;
|
|
104
|
+
pointer-events:none;background:#f3ead8;color:#17202b;border:2px solid #17202b;
|
|
105
|
+
border-top-color:var(--speech);border-radius:8px 8px 8px 2px;
|
|
106
|
+
box-shadow:3px 4px 0 rgba(0,0,0,.32);font:500 12px/1.38 var(--hud);
|
|
107
|
+
overflow-wrap:anywhere;animation:speechIn .16s ease-out,speechOut .35s ease-in 7.25s forwards}
|
|
108
|
+
.gameSpeech::before{content:"";position:absolute;left:24px;bottom:-10px;width:13px;height:13px;
|
|
109
|
+
background:#f3ead8;border-right:2px solid #17202b;border-bottom:2px solid #17202b;
|
|
110
|
+
transform:skewY(35deg) rotate(45deg)}
|
|
111
|
+
.gameSpeech .to{display:block;margin-bottom:3px;color:#8a5c00;font-size:10px;font-weight:700;
|
|
112
|
+
letter-spacing:.05em;text-transform:uppercase}
|
|
113
|
+
.gameSpeech[data-tone="floor"]{--speech:#aa93ff}
|
|
114
|
+
.gameSpeech[data-tone="decision"],.gameSpeech[data-tone="verification"]{--speech:#4ec98a}
|
|
115
|
+
.gameSpeech[data-tone="error"]{--speech:#e2604f}
|
|
116
|
+
@keyframes speechIn{from{opacity:0;transform:translate(-50%,calc(-100% - 4px)) scale(.92)}to{opacity:1}}
|
|
117
|
+
@keyframes speechOut{to{opacity:0;transform:translate(-50%,calc(-100% - 18px))}}
|
|
118
|
+
@media(prefers-reduced-motion:reduce){.gameSpeech{animation:none}}
|
|
119
|
+
#ficha{position:relative;overflow:visible;margin:0 14px;background:var(--suelo2);
|
|
120
|
+
border:1px solid var(--linea2);padding:14px 15px;border-radius:3px;font-size:13px}
|
|
121
|
+
#ficha[hidden]{display:none}
|
|
122
|
+
#peli{display:flex;flex-direction:column;align-items:stretch;gap:9px;padding-top:12px;
|
|
123
|
+
border-top:1px solid var(--linea)}
|
|
124
|
+
#peli[hidden]{display:none}
|
|
125
|
+
#play{width:34px;height:34px;flex:0 0 34px;border:1px solid var(--linea2);border-radius:50%;
|
|
126
|
+
background:var(--suelo2);color:var(--lampara);font-size:12px;cursor:pointer;line-height:1}
|
|
127
|
+
#play:hover{border-color:var(--lampara)}
|
|
128
|
+
.pel-txt{min-width:210px}
|
|
129
|
+
#diaTexto{font-family:var(--hud);font-size:15px;font-weight:600;letter-spacing:.06em}
|
|
130
|
+
#diaDato{font-family:var(--mono);font-size:10.5px;color:var(--tinta3)}
|
|
131
|
+
#dia{width:100%;accent-color:var(--lampara);height:3px}
|
|
132
|
+
.fechas{font-family:var(--mono);font-size:10.5px;color:var(--tinta3);display:flex;
|
|
133
|
+
align-items:center;gap:5px;white-space:nowrap}
|
|
134
|
+
.fechas input{background:var(--suelo2);border:1px solid var(--linea2);color:var(--tinta2);
|
|
135
|
+
border-radius:3px;padding:3px 5px;font-family:var(--mono);font-size:10.5px;color-scheme:dark}
|
|
136
|
+
#ficha h3{margin:0 0 8px;font-family:var(--mono);font-size:14px;font-weight:500;word-break:break-all}
|
|
137
|
+
/* The agent's face on its card, and the door to the Hall's section. */
|
|
138
|
+
.fichaCara{width:22px;height:22px;border-radius:5px;vertical-align:-6px;margin-right:7px}
|
|
139
|
+
.irHall{border:1px solid var(--linea2);background:var(--suelo);color:var(--tinta2);
|
|
140
|
+
font-family:var(--mono);font-size:11px;padding:6px 10px;border-radius:4px;cursor:pointer}
|
|
141
|
+
.irHall:hover{color:var(--lampara);border-color:#65552d}
|
|
142
|
+
/* The live bus is useful, but it is persistent HUD and therefore belongs in a
|
|
143
|
+
sidebar. The city never loses vertical space to a tape across the canvas. */
|
|
144
|
+
#cinta{display:flex;flex-direction:column;gap:8px;padding-top:12px;
|
|
145
|
+
border-top:1px solid var(--linea);overflow:hidden}
|
|
146
|
+
#cinta .rotulo{font-family:var(--hud);font-size:9px;letter-spacing:.14em;
|
|
147
|
+
text-transform:uppercase;color:var(--tinta3)}
|
|
148
|
+
#cinta .ventana{overflow:hidden;mask-image:linear-gradient(90deg,transparent,#000 12px,#000 94%,transparent)}
|
|
149
|
+
#cinta .tira{display:inline-flex;gap:26px;white-space:nowrap;padding-left:6px;
|
|
150
|
+
min-height:24px;align-items:center;animation:desliza linear infinite;will-change:transform}
|
|
151
|
+
#cinta .tira:hover{animation-play-state:paused}
|
|
152
|
+
#cinta .uno{display:inline-flex;align-items:center;gap:7px;font-family:var(--mono);font-size:11.5px;color:var(--tinta2)}
|
|
153
|
+
#cinta .uno .of2{color:var(--tinta3);font-family:var(--texto);font-style:italic}
|
|
154
|
+
#cinta .uno .en{color:var(--lampara)}
|
|
155
|
+
#cinta .nadie{font-family:var(--texto);font-size:12.5px;color:var(--tinta3);font-style:italic}
|
|
156
|
+
@keyframes desliza{from{transform:translateX(0)}to{transform:translateX(-50%)}}
|
|
157
|
+
@media(prefers-reduced-motion:reduce){#cinta .tira{animation:none}}
|
|
158
|
+
|
|
159
|
+
#mandos{display:flex;gap:6px;padding-top:12px;border-top:1px solid var(--linea)}
|
|
160
|
+
#mandos button{width:34px;height:34px;border-radius:3px;cursor:pointer;
|
|
161
|
+
background:var(--suelo);border:1px solid var(--linea);color:var(--tinta2);
|
|
162
|
+
font:600 15px var(--hud);display:grid;place-items:center}
|
|
163
|
+
#mandos button:hover{color:var(--tinta);border-color:var(--tinta3)}
|
|
164
|
+
#mandos button:focus-visible{outline:1px solid var(--lampara);outline-offset:2px}
|
|
165
|
+
#mandos button.on{color:var(--lampara);border-color:var(--lampara)}
|
|
166
|
+
#ficha #cerrar{position:absolute;right:8px;top:7px;width:22px;height:22px;border:0;
|
|
167
|
+
border-radius:3px;background:transparent;color:var(--tinta3);cursor:pointer;
|
|
168
|
+
font:400 18px/1 var(--hud);display:grid;place-items:center}
|
|
169
|
+
#ficha #cerrar:hover{color:var(--tinta);background:rgba(255,255,255,.06)}
|
|
170
|
+
#ficha #cerrar:focus-visible{outline:1px solid var(--lampara);outline-offset:1px}
|
|
171
|
+
#ficha h3{padding-right:22px}
|
|
172
|
+
#ficha dl{margin:0;display:grid;grid-template-columns:auto 1fr;gap:5px 12px}
|
|
173
|
+
#ficha h3 .pip{display:inline-block;vertical-align:middle;margin-right:6px}
|
|
174
|
+
#ficha .lista{margin-top:9px;display:flex;flex-direction:column;gap:3px}
|
|
175
|
+
#ficha .lista .ag{display:flex;gap:10px;font-family:var(--mono);font-size:11px}
|
|
176
|
+
#ficha .lista .an{color:var(--tinta2)}
|
|
177
|
+
#ficha .lista .do{color:var(--tinta3);margin-left:auto}
|
|
178
|
+
#ficha .metas{margin-top:12px;display:flex;flex-direction:column;gap:10px}
|
|
179
|
+
#ficha .meta{border-top:1px solid var(--linea);padding-top:9px}
|
|
180
|
+
#ficha .mt{display:flex;gap:8px;align-items:baseline;margin-bottom:5px}
|
|
181
|
+
#ficha .mt b{font-size:13px;line-height:1.35;color:var(--tinta)}
|
|
182
|
+
#ficha .est{flex:0 0 auto;font-family:var(--hud);font-size:8.5px;letter-spacing:.1em;
|
|
183
|
+
text-transform:uppercase;border:1px solid currentColor;border-radius:2px;padding:1px 5px;
|
|
184
|
+
color:var(--tinta3)}
|
|
185
|
+
#ficha .est.in-progress{color:var(--lampara)}
|
|
186
|
+
#ficha .est.at-risk{color:var(--carta)}
|
|
187
|
+
#ficha .est.done{color:#3fb8a0}
|
|
188
|
+
#ficha .ml{display:flex;gap:8px;font-size:11.5px;line-height:1.45;color:var(--tinta2);margin-top:2px}
|
|
189
|
+
#ficha .ml span{flex:0 0 74px;font-family:var(--hud);font-size:8.5px;letter-spacing:.09em;
|
|
190
|
+
text-transform:uppercase;color:var(--tinta3);padding-top:3px}
|
|
191
|
+
#ficha .ml code{font-family:var(--mono);font-size:10.5px;color:var(--tinta3);word-break:break-all}
|
|
192
|
+
#cinta .uno{cursor:pointer}
|
|
193
|
+
#cinta .uno:hover{color:var(--tinta)}
|
|
194
|
+
#ficha dt{font-family:var(--hud);font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--tinta3);padding-top:3px}
|
|
195
|
+
#ficha .aviso2{margin:10px 0 0;font-size:11.5px;line-height:1.45;color:#c79a4e;border-top:1px solid var(--linea);padding-top:9px}
|
|
196
|
+
#ficha .aviso2 code{font-family:var(--mono);font-size:11px}
|
|
197
|
+
#ficha dd{margin:0;font-family:var(--mono);font-size:11.5px}
|
|
198
|
+
</style>
|
|
199
|
+
</head>
|
|
200
|
+
<body>
|
|
201
|
+
<div id="app">
|
|
202
|
+
<aside>
|
|
203
|
+
<div>
|
|
204
|
+
<span class="badge">Spectator mode</span>
|
|
205
|
+
<h1>Agents City</h1>
|
|
206
|
+
<p class="sub">Every house is a parcel: a slice of a repo that serves one business unit. It grows a floor when a PR lands, and what sits in scaffolding is work stuck in the open.</p>
|
|
207
|
+
</div>
|
|
208
|
+
<div class="cifras">
|
|
209
|
+
<div class="cifra"><b id="c-casas">—</b><span>parcels</span></div>
|
|
210
|
+
<div class="cifra"><b id="c-conectados">—</b><span>on the bus</span></div>
|
|
211
|
+
<div class="cifra viva"><b id="c-luces">—</b><span>digging</span></div>
|
|
212
|
+
</div>
|
|
213
|
+
<div id="cinta" aria-live="polite">
|
|
214
|
+
<span class="rotulo">On the bus</span>
|
|
215
|
+
<div class="ventana"><div class="tira" id="tira"></div></div>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="gasto" id="gasto" hidden>
|
|
218
|
+
<div class="gt">
|
|
219
|
+
<b id="g-hoy">—</b>
|
|
220
|
+
<span>tokens spent today, whole city</span>
|
|
221
|
+
</div>
|
|
222
|
+
<div class="gm">
|
|
223
|
+
<span id="g-mes">—</span> in the last 30 days ·
|
|
224
|
+
<span id="g-cache">—</span> of it served from cache
|
|
225
|
+
</div>
|
|
226
|
+
<p class="gn">One number for everyone. There is no per-person view, and
|
|
227
|
+
there is not going to be one.</p>
|
|
228
|
+
</div>
|
|
229
|
+
<div>
|
|
230
|
+
<h2>Districts</h2>
|
|
231
|
+
<div class="leyenda" id="leyenda"></div>
|
|
232
|
+
</div>
|
|
233
|
+
<div>
|
|
234
|
+
<h2>Who is digging</h2>
|
|
235
|
+
<ul id="cuadrillas"><li class="vacio">—</li></ul>
|
|
236
|
+
</div>
|
|
237
|
+
<div>
|
|
238
|
+
<h2>Map controls</h2>
|
|
239
|
+
<div id="mandos">
|
|
240
|
+
<button id="m-mas" title="Zoom in (+)">+</button>
|
|
241
|
+
<button id="m-menos" title="Zoom out (−)">−</button>
|
|
242
|
+
<button id="m-encaja" title="Fit the whole city (F)">⤢</button>
|
|
243
|
+
<button id="m-sigue" title="Follow the action (G)" class="on">◎</button>
|
|
244
|
+
<button id="m-full" title="Fullscreen (P)">⛶</button>
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
<div id="peli" hidden>
|
|
248
|
+
<h2>City history</h2>
|
|
249
|
+
<div style="display:flex;align-items:center;gap:10px">
|
|
250
|
+
<button id="play" title="Play the history of the city">▶</button>
|
|
251
|
+
<div class="pel-txt"><div id="diaTexto">—</div><div id="diaDato"></div></div>
|
|
252
|
+
</div>
|
|
253
|
+
<input id="dia" type="range" min="0" max="0" value="0" aria-label="Day">
|
|
254
|
+
<label class="fechas">from <input id="desde" type="date"></label>
|
|
255
|
+
<label class="fechas">to <input id="hasta" type="date"></label>
|
|
256
|
+
</div>
|
|
257
|
+
<details id="clave">
|
|
258
|
+
<summary>How to read the map</summary>
|
|
259
|
+
<dl>
|
|
260
|
+
<dt>a floor</dt><dd>a pull request that landed. Houses grow when work
|
|
261
|
+
lands, not when it starts</dd>
|
|
262
|
+
<dt>scaffolding</dt><dd>an open pull request. Amber once it has been open
|
|
263
|
+
more than two weeks</dd>
|
|
264
|
+
<dt>a crane</dt><dd>five or more open at once: a genuinely big site</dd>
|
|
265
|
+
<dt>a red crack</dt><dd>CI is failing</dd>
|
|
266
|
+
<dt>lit windows</dt><dd>the parcel moved this month</dd>
|
|
267
|
+
<dt>pallets</dt><dd>commits with no pull request, waiting to be laid</dd>
|
|
268
|
+
<dt>a worker</dt><dd>one window or worktree of one person, digging in
|
|
269
|
+
that parcel right now</dd>
|
|
270
|
+
<dt>the square</dt><dd>on the bus, nothing started yet</dd>
|
|
271
|
+
</dl>
|
|
272
|
+
<p>The lab is drawn with permanent scaffolding: it is an open site on
|
|
273
|
+
purpose, and its growth is not compared against the rest.</p>
|
|
274
|
+
</details>
|
|
275
|
+
<div class="estado"><span class="punto" id="punto"></span><span id="conex">connecting…</span></div>
|
|
276
|
+
</aside>
|
|
277
|
+
<main aria-label="City map"></main>
|
|
278
|
+
<aside id="avisos">
|
|
279
|
+
<header>
|
|
280
|
+
<h2>Notices between agents <b id="c-cartas"></b></h2>
|
|
281
|
+
<p>An agent telling the owner of a property that a change touched it. Before
|
|
282
|
+
the merge, when changing your mind is still cheap.</p>
|
|
283
|
+
</header>
|
|
284
|
+
<div id="ficha" hidden>
|
|
285
|
+
<button id="cerrar" title="Close (Esc)" aria-label="Close">×</button>
|
|
286
|
+
<div id="ficha-cuerpo"></div>
|
|
287
|
+
</div>
|
|
288
|
+
<ul id="tt"></ul>
|
|
289
|
+
<p class="vacio2" id="tt-vacio">Nothing yet. Press play and watch them fly.</p>
|
|
290
|
+
</aside>
|
|
291
|
+
</div>
|
|
292
|
+
<script type="module" src="/city.js"></script>
|
|
293
|
+
</body>
|
|
294
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "city-web",
|
|
3
|
+
"private": true,
|
|
4
|
+
"scripts": {
|
|
5
|
+
"build": "esbuild src/main.ts --bundle --minify --format=esm --outfile=dist/city.js && cp index.html dist/index.html && mkdir -p dist/modelos dist/sprites && cp -R assets/modelos/. dist/modelos/ && cp -R assets/sprites/. dist/sprites/ && python3 sella.py && esbuild src/hall.ts --bundle --minify --format=esm --outfile=dist-hall/hall.js",
|
|
6
|
+
"watch": "esbuild src/main.ts --bundle --format=esm --outfile=dist/city.js --watch",
|
|
7
|
+
"typecheck": "tsc --noEmit --strict --target es2022 --module esnext --moduleResolution bundler --skipLibCheck src/main.ts src/oven.ts src/hall.ts"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@types/three": "^0.185.4",
|
|
11
|
+
"pixi.js": "^8.6.6",
|
|
12
|
+
"three": "^0.185.1"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"esbuild": "^0.24.0",
|
|
16
|
+
"typescript": "^5.7.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Stamp the bundle's fingerprint into the page that loads it.
|
|
3
|
+
|
|
4
|
+
The bundle is always called city.js, so a browser that has seen it once keeps
|
|
5
|
+
serving its own copy — you pull, you rebuild, you reload, and you are still
|
|
6
|
+
looking at last week's map with no way to tell. This rewrites the script tag in
|
|
7
|
+
dist/index.html to /city.js?v=<hash of the bundle>, so a changed bundle is a
|
|
8
|
+
changed URL and the browser fetches it. An unchanged bundle keeps its hash and
|
|
9
|
+
stays cached, which is the whole point of the cache.
|
|
10
|
+
"""
|
|
11
|
+
import hashlib, pathlib, re, sys
|
|
12
|
+
|
|
13
|
+
dist = pathlib.Path(__file__).parent / 'dist'
|
|
14
|
+
for pagina, guion in (('index.html', 'city.js'),):
|
|
15
|
+
p, j = dist / pagina, dist / guion
|
|
16
|
+
if not p.exists() or not j.exists():
|
|
17
|
+
continue
|
|
18
|
+
v = hashlib.sha1(j.read_bytes()).hexdigest()[:10]
|
|
19
|
+
t = re.sub(rf'(src="/{guion})(\?v=[a-f0-9]+)?"', rf'\1?v={v}"', p.read_text())
|
|
20
|
+
p.write_text(t)
|
|
21
|
+
print(f' {pagina} -> {guion}?v={v}', file=sys.stderr)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Container } from 'pixi.js';
|
|
2
|
+
import { ActivityEvent } from './activity';
|
|
3
|
+
import { arquitecto, perito } from './people';
|
|
4
|
+
|
|
5
|
+
export interface ActorPosition {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Materialises only speakers that the map's older presence feed does not know
|
|
11
|
+
* about yet. Each figure is backed by a real activity event and placed beside
|
|
12
|
+
* its repo; no random NPC conversation is invented. */
|
|
13
|
+
export class ActivityActors {
|
|
14
|
+
private created = new Map<string, Container>();
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly layer: Container,
|
|
18
|
+
private readonly findExisting: (actor: string) => Container | null,
|
|
19
|
+
private readonly positionFor: (event: ActivityEvent) => ActorPosition,
|
|
20
|
+
private readonly decorate?: (figure: Container, actor: string) => void,
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
resolve = (event: ActivityEvent): Container => {
|
|
24
|
+
const actor = event.actor.trim();
|
|
25
|
+
const existing = this.findExisting(actor);
|
|
26
|
+
if (existing) {
|
|
27
|
+
const synthetic = this.created.get(actor);
|
|
28
|
+
if (synthetic) {
|
|
29
|
+
synthetic.parent?.removeChild(synthetic);
|
|
30
|
+
synthetic.destroy({ children: true });
|
|
31
|
+
this.created.delete(actor);
|
|
32
|
+
}
|
|
33
|
+
this.decorate?.(existing, actor);
|
|
34
|
+
return existing;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const known = this.created.get(actor);
|
|
38
|
+
if (known) return known;
|
|
39
|
+
const figure = event.role === 'chair' ? arquitecto(actor) : perito(actor, 'repo agent');
|
|
40
|
+
const point = this.positionFor(event);
|
|
41
|
+
figure.position.set(point.x, point.y);
|
|
42
|
+
this.layer.addChild(figure);
|
|
43
|
+
this.created.set(actor, figure);
|
|
44
|
+
this.decorate?.(figure, actor);
|
|
45
|
+
return figure;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
export type ActivityTone =
|
|
2
|
+
'question' | 'work' | 'floor' | 'evidence' | 'decision' | 'verification' | 'error' | 'system';
|
|
3
|
+
|
|
4
|
+
export interface ActivityEvent {
|
|
5
|
+
protocol: 'agents-city-activity/1';
|
|
6
|
+
id: string;
|
|
7
|
+
seq: number;
|
|
8
|
+
city: string;
|
|
9
|
+
thread: string | null;
|
|
10
|
+
kind: string;
|
|
11
|
+
actor: string;
|
|
12
|
+
role: 'chair' | 'member';
|
|
13
|
+
phase: string;
|
|
14
|
+
tone: ActivityTone;
|
|
15
|
+
title: string;
|
|
16
|
+
summary: string;
|
|
17
|
+
details: string[];
|
|
18
|
+
target?: string;
|
|
19
|
+
at: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const MAP_ACTIVITY_PROTOCOL = 'agents-city-map-activity/1' as const;
|
|
23
|
+
|
|
24
|
+
export interface MapActivityMessage {
|
|
25
|
+
protocol: typeof MAP_ACTIVITY_PROTOCOL;
|
|
26
|
+
type: 'activity.event';
|
|
27
|
+
event: ActivityEvent;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** One road, as the map needs it: the name people use and the stable address. */
|
|
31
|
+
export interface RoadInfo {
|
|
32
|
+
name: string;
|
|
33
|
+
address: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** One agent, as the map needs it: its name and its kind, which decides the
|
|
37
|
+
* family of building its parcel wears. */
|
|
38
|
+
export interface AgentInfo {
|
|
39
|
+
name: string;
|
|
40
|
+
kind: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The Hall tells the embedded map what only the Hall knows: this city's
|
|
44
|
+
* roads, its agents' kinds, and their faces. The standalone map (the worker's,
|
|
45
|
+
* for the whole team) never gets this message and falls back to what its own
|
|
46
|
+
* data says — it does not invent connections, kinds or faces. */
|
|
47
|
+
export interface MapConfigMessage {
|
|
48
|
+
protocol: typeof MAP_ACTIVITY_PROTOCOL;
|
|
49
|
+
type: 'map.config';
|
|
50
|
+
roads: RoadInfo[];
|
|
51
|
+
agents?: AgentInfo[];
|
|
52
|
+
avatars?: Record<string, string>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The embedded map asking its Hall to change section — a click on the town
|
|
56
|
+
* hall opens the committee, a click on a gate opens the roads. */
|
|
57
|
+
export interface MapNavMessage {
|
|
58
|
+
protocol: typeof MAP_ACTIVITY_PROTOCOL;
|
|
59
|
+
type: 'map.nav';
|
|
60
|
+
view: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isMapConfigMessage(value: unknown): value is MapConfigMessage {
|
|
64
|
+
if (!value || typeof value !== 'object') return false;
|
|
65
|
+
const message = value as Partial<MapConfigMessage>;
|
|
66
|
+
return (
|
|
67
|
+
message.protocol === MAP_ACTIVITY_PROTOCOL &&
|
|
68
|
+
message.type === 'map.config' &&
|
|
69
|
+
Array.isArray(message.roads) &&
|
|
70
|
+
message.roads.every(
|
|
71
|
+
(road) => Boolean(road) && typeof road.name === 'string' && typeof road.address === 'string',
|
|
72
|
+
) &&
|
|
73
|
+
(message.agents === undefined ||
|
|
74
|
+
(Array.isArray(message.agents) &&
|
|
75
|
+
message.agents.every(
|
|
76
|
+
(agent) =>
|
|
77
|
+
Boolean(agent) && typeof agent.name === 'string' && typeof agent.kind === 'string',
|
|
78
|
+
))) &&
|
|
79
|
+
(message.avatars === undefined ||
|
|
80
|
+
(typeof message.avatars === 'object' &&
|
|
81
|
+
message.avatars !== null &&
|
|
82
|
+
Object.values(message.avatars).every((uri) => typeof uri === 'string')))
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function isMapNavMessage(value: unknown): value is MapNavMessage {
|
|
87
|
+
if (!value || typeof value !== 'object') return false;
|
|
88
|
+
const message = value as Partial<MapNavMessage>;
|
|
89
|
+
return (
|
|
90
|
+
message.protocol === MAP_ACTIVITY_PROTOCOL &&
|
|
91
|
+
message.type === 'map.nav' &&
|
|
92
|
+
typeof message.view === 'string'
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function isActivityEvent(value: unknown): value is ActivityEvent {
|
|
97
|
+
if (!value || typeof value !== 'object') return false;
|
|
98
|
+
const event = value as Partial<ActivityEvent>;
|
|
99
|
+
return (
|
|
100
|
+
event.protocol === 'agents-city-activity/1' &&
|
|
101
|
+
typeof event.id === 'string' &&
|
|
102
|
+
typeof event.seq === 'number' &&
|
|
103
|
+
typeof event.kind === 'string' &&
|
|
104
|
+
typeof event.actor === 'string' &&
|
|
105
|
+
typeof event.summary === 'string' &&
|
|
106
|
+
typeof event.title === 'string' &&
|
|
107
|
+
Array.isArray(event.details)
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function isMapActivityMessage(value: unknown): value is MapActivityMessage {
|
|
112
|
+
if (!value || typeof value !== 'object') return false;
|
|
113
|
+
const message = value as Partial<MapActivityMessage>;
|
|
114
|
+
return (
|
|
115
|
+
message.protocol === MAP_ACTIVITY_PROTOCOL &&
|
|
116
|
+
message.type === 'activity.event' &&
|
|
117
|
+
isActivityEvent(message.event)
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The lifecycle beats the map's presence layer runs on: a turn opening, a turn
|
|
122
|
+
* closing, a session ending. Not speech — nothing here becomes a bubble — but
|
|
123
|
+
* without them the lights on the map can only describe last month. */
|
|
124
|
+
export function isPresenceEvent(event: ActivityEvent): boolean {
|
|
125
|
+
return (
|
|
126
|
+
event.kind === 'conversation.user' ||
|
|
127
|
+
event.kind === 'conversation.agent' ||
|
|
128
|
+
event.kind === 'runtime.session.started' ||
|
|
129
|
+
event.kind === 'runtime.session.ended'
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Only real, human-readable turns become game speech. Commands, lifecycle
|
|
134
|
+
* noise and sealed positions remain in the optional work feed. */
|
|
135
|
+
export function isSpeechEvent(event: ActivityEvent): boolean {
|
|
136
|
+
if (event.kind === 'conversation.agent' || event.kind === 'conversation.agent.commentary') {
|
|
137
|
+
return Boolean(event.summary.trim());
|
|
138
|
+
}
|
|
139
|
+
return (
|
|
140
|
+
event.kind === 'committee.opened' ||
|
|
141
|
+
event.kind === 'committee.position.revealed' ||
|
|
142
|
+
event.kind === 'committee.synthesis.published' ||
|
|
143
|
+
event.kind.startsWith('committee.floor.') ||
|
|
144
|
+
event.kind === 'committee.decision.recorded' ||
|
|
145
|
+
event.kind.startsWith('committee.verification.') ||
|
|
146
|
+
event.kind === 'committee.replanned' ||
|
|
147
|
+
event.kind === 'committee.closed' ||
|
|
148
|
+
(event.kind === 'committee.command.rejected' && event.tone === 'error')
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function speechRecipient(event: ActivityEvent): string {
|
|
153
|
+
const target = event.target?.trim();
|
|
154
|
+
if (target) return target;
|
|
155
|
+
if (event.role === 'member') return 'seat';
|
|
156
|
+
if (event.kind === 'conversation.agent') return 'you';
|
|
157
|
+
return 'committee';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** A bubble is a glance, not the transcript. The full, unabridged event stays
|
|
161
|
+
* in the right sidebar. */
|
|
162
|
+
export function compactSpeech(value: string, max = 156): string {
|
|
163
|
+
const text = value.replace(/\s+/g, ' ').trim();
|
|
164
|
+
if (text.length <= max) return text;
|
|
165
|
+
const sentence = text
|
|
166
|
+
.slice(0, max + 1)
|
|
167
|
+
.match(/^.{42,}?[.!?](?:\s|$)/)?.[0]
|
|
168
|
+
?.trim();
|
|
169
|
+
if (sentence && sentence.length <= max) return sentence;
|
|
170
|
+
const boundary = text.slice(0, max).lastIndexOf(' ');
|
|
171
|
+
return text.slice(0, boundary > max * 0.65 ? boundary : max).trimEnd() + '…';
|
|
172
|
+
}
|