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,69 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Append secret-scrubbed operational diagnostics for one local city."""
|
|
3
|
+
import argparse
|
|
4
|
+
import datetime
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import uuid
|
|
9
|
+
|
|
10
|
+
import runtime_processes
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _clean(value, limit=2000):
|
|
14
|
+
text = str(value or '').strip()
|
|
15
|
+
text = re.sub(r'([?&](?:token|secret|key)=)[^&\s]+', r'\1[redacted]', text,
|
|
16
|
+
flags=re.IGNORECASE)
|
|
17
|
+
text = re.sub(r'Bearer\s+\S+', 'Bearer [redacted]', text, flags=re.IGNORECASE)
|
|
18
|
+
text = re.sub(r'(Authorization:\s*)\S+(?:\s+\S+)?', r'\1[redacted]', text,
|
|
19
|
+
flags=re.IGNORECASE)
|
|
20
|
+
text = re.sub(r'(--(?:token|password|secret|api-key)(?:=|\s+))\S+',
|
|
21
|
+
r'\1[redacted]', text, flags=re.IGNORECASE)
|
|
22
|
+
text = re.sub(r'((?:TOKEN|SECRET|PASSWORD|API_KEY)=)[^\s]+',
|
|
23
|
+
r'\1[redacted]', text, flags=re.IGNORECASE)
|
|
24
|
+
return text[:limit]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def append(data, component, event, **fields):
|
|
28
|
+
"""Best-effort logging: diagnostics must never stop an agent."""
|
|
29
|
+
try:
|
|
30
|
+
directory = runtime_processes.ruta(data)
|
|
31
|
+
os.makedirs(directory, mode=0o700, exist_ok=True)
|
|
32
|
+
path = os.path.join(directory, 'diagnostics.jsonl')
|
|
33
|
+
record = {
|
|
34
|
+
'protocol': 'agents-city-diagnostic/1',
|
|
35
|
+
'id': 'diagnostic_' + uuid.uuid4().hex,
|
|
36
|
+
'at': datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
|
37
|
+
'pid': os.getpid(),
|
|
38
|
+
'component': _clean(component, 80),
|
|
39
|
+
'event': _clean(event, 120),
|
|
40
|
+
}
|
|
41
|
+
for key, value in fields.items():
|
|
42
|
+
if value is None:
|
|
43
|
+
continue
|
|
44
|
+
record[key] = ('[redacted]' if re.search(
|
|
45
|
+
r'token|secret|authorization|credential', key, re.IGNORECASE)
|
|
46
|
+
else _clean(value))
|
|
47
|
+
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
|
|
48
|
+
with os.fdopen(fd, 'a', encoding='utf-8') as stream:
|
|
49
|
+
stream.write(json.dumps(record, ensure_ascii=False) + '\n')
|
|
50
|
+
except Exception:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def main():
|
|
55
|
+
parser = argparse.ArgumentParser()
|
|
56
|
+
parser.add_argument('event')
|
|
57
|
+
parser.add_argument('--data', default=os.environ.get('AGENTS_CITY_DATA', ''))
|
|
58
|
+
parser.add_argument('--component', default='launcher')
|
|
59
|
+
parser.add_argument('--actor', default=os.environ.get('CITY_BUS_ACTOR', ''))
|
|
60
|
+
parser.add_argument('--outcome', default='')
|
|
61
|
+
parser.add_argument('--message', default='')
|
|
62
|
+
args = parser.parse_args()
|
|
63
|
+
if args.data:
|
|
64
|
+
append(args.data, args.component, args.event, actor=args.actor,
|
|
65
|
+
outcome=args.outcome, message=args.message)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if __name__ == '__main__':
|
|
69
|
+
main()
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Exact process markers for one city's local bus and runtime gateways."""
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shlex
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
import cities
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def ruta(datos):
|
|
13
|
+
ident = cities.identidad(datos)
|
|
14
|
+
key = re.sub(r'[^a-z0-9-]+', '-', ident.lower()).strip('-')[:80] or 'city'
|
|
15
|
+
return os.path.join(cities.raiz(), '.runtime', 'bus', key)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def procesos(datos):
|
|
19
|
+
"""Return every exact city runtime, including one that lost its marker.
|
|
20
|
+
|
|
21
|
+
Marker files are the fast path. A crashed or racing predecessor can lose
|
|
22
|
+
its endpoint while the process survives, so also inspect argv and accept
|
|
23
|
+
only a known entrypoint whose literal ``--data`` resolves to this city.
|
|
24
|
+
"""
|
|
25
|
+
datos = os.path.realpath(datos)
|
|
26
|
+
base = ruta(datos)
|
|
27
|
+
candidatos = []
|
|
28
|
+
try:
|
|
29
|
+
endpoint = json.load(open(os.path.join(base, 'endpoint.json'), encoding='utf-8'))
|
|
30
|
+
candidatos.append(('bus hub', int(endpoint.get('pid', 0)), 'local-hub.js'))
|
|
31
|
+
except (OSError, ValueError, TypeError, json.JSONDecodeError):
|
|
32
|
+
pass
|
|
33
|
+
for folder, label, marker in (
|
|
34
|
+
('gateways', 'gateway', 'runtime-gateway.js'),
|
|
35
|
+
('adapters', 'terminal fallback', 'adapter.js')):
|
|
36
|
+
directory = os.path.join(base, folder)
|
|
37
|
+
try:
|
|
38
|
+
nombres = [n for n in os.listdir(directory) if n.endswith('.pid')]
|
|
39
|
+
except OSError:
|
|
40
|
+
nombres = []
|
|
41
|
+
for nombre in nombres:
|
|
42
|
+
try:
|
|
43
|
+
pid = int(open(os.path.join(directory, nombre), encoding='utf-8').read().strip())
|
|
44
|
+
except (OSError, ValueError):
|
|
45
|
+
continue
|
|
46
|
+
candidatos.append((f'{label} {nombre[:-4]}', pid, marker))
|
|
47
|
+
|
|
48
|
+
vistos = set()
|
|
49
|
+
fuera = []
|
|
50
|
+
for etiqueta, pid, componente in candidatos:
|
|
51
|
+
if pid <= 0:
|
|
52
|
+
continue
|
|
53
|
+
try:
|
|
54
|
+
comando = subprocess.run(
|
|
55
|
+
['ps', '-p', str(pid), '-o', 'command='],
|
|
56
|
+
capture_output=True, text=True, timeout=3).stdout.strip()
|
|
57
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
58
|
+
comando = ''
|
|
59
|
+
if componente in comando and datos in comando:
|
|
60
|
+
fuera.append({'label': etiqueta, 'pid': pid})
|
|
61
|
+
vistos.add(pid)
|
|
62
|
+
for proceso in _procesos_sin_marca(datos):
|
|
63
|
+
if proceso['pid'] not in vistos:
|
|
64
|
+
fuera.append(proceso)
|
|
65
|
+
vistos.add(proceso['pid'])
|
|
66
|
+
return fuera
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _procesos_sin_marca(datos):
|
|
70
|
+
"""Discover only node entrypoints with an exact ``--data <city>`` argv."""
|
|
71
|
+
try:
|
|
72
|
+
salida = subprocess.run(
|
|
73
|
+
['ps', '-Ao', 'pid=,command='], capture_output=True, text=True,
|
|
74
|
+
timeout=5).stdout
|
|
75
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
76
|
+
return []
|
|
77
|
+
componentes = {
|
|
78
|
+
'local-hub.js': 'bus hub',
|
|
79
|
+
'runtime-gateway.js': 'gateway',
|
|
80
|
+
'adapter.js': 'terminal fallback',
|
|
81
|
+
}
|
|
82
|
+
encontrados = []
|
|
83
|
+
for linea in salida.splitlines():
|
|
84
|
+
match = re.match(r'\s*(\d+)\s+(.*)', linea)
|
|
85
|
+
if not match:
|
|
86
|
+
continue
|
|
87
|
+
pid, comando = int(match.group(1)), match.group(2)
|
|
88
|
+
if pid in (os.getpid(), os.getppid()):
|
|
89
|
+
continue
|
|
90
|
+
try:
|
|
91
|
+
argv = shlex.split(comando)
|
|
92
|
+
except ValueError:
|
|
93
|
+
continue
|
|
94
|
+
indice = next(
|
|
95
|
+
(i for i, value in enumerate(argv[:3])
|
|
96
|
+
if os.path.basename(value) in componentes), None)
|
|
97
|
+
if indice is None:
|
|
98
|
+
continue
|
|
99
|
+
try:
|
|
100
|
+
data_index = argv.index('--data', indice + 1)
|
|
101
|
+
data = os.path.realpath(argv[data_index + 1])
|
|
102
|
+
except (ValueError, IndexError):
|
|
103
|
+
continue
|
|
104
|
+
if data != datos:
|
|
105
|
+
continue
|
|
106
|
+
componente = os.path.basename(argv[indice])
|
|
107
|
+
etiqueta = componentes[componente]
|
|
108
|
+
if componente in ('runtime-gateway.js', 'adapter.js'):
|
|
109
|
+
try:
|
|
110
|
+
actor = argv[argv.index('--actor', indice + 1) + 1]
|
|
111
|
+
etiqueta = f'{etiqueta} {actor}'
|
|
112
|
+
except (ValueError, IndexError):
|
|
113
|
+
pass
|
|
114
|
+
encontrados.append({'label': etiqueta, 'pid': pid})
|
|
115
|
+
return encontrados
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def limpia_marcas(datos):
|
|
119
|
+
base = ruta(datos)
|
|
120
|
+
rutas = [os.path.join(base, 'endpoint.json'), os.path.join(base, 'hub.lock')]
|
|
121
|
+
for folder in ('gateways', 'adapters'):
|
|
122
|
+
directory = os.path.join(base, folder)
|
|
123
|
+
try:
|
|
124
|
+
rutas += [os.path.join(directory, n) for n in os.listdir(directory)
|
|
125
|
+
if n.endswith('.pid')]
|
|
126
|
+
except OSError:
|
|
127
|
+
pass
|
|
128
|
+
for path in rutas:
|
|
129
|
+
try:
|
|
130
|
+
os.unlink(path)
|
|
131
|
+
except OSError:
|
|
132
|
+
pass
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Path containment, resolved twice, in one place.
|
|
3
|
+
|
|
4
|
+
Every part of the security layer that trusts a path — the cage deciding what to
|
|
5
|
+
re-allow for writing, the broker deciding which repo a token may act on — needs
|
|
6
|
+
the same three questions answered the same way:
|
|
7
|
+
|
|
8
|
+
1. Where does this path *really* point, even if its leaf does not exist yet?
|
|
9
|
+
A non-existent leaf under a symlinked parent still redirects, so we resolve
|
|
10
|
+
through the deepest ancestor that does exist.
|
|
11
|
+
2. Is it inside a forbidden root?
|
|
12
|
+
3. Does it *cover* a forbidden root (an ancestor of it)? Re-allowing `~` would
|
|
13
|
+
silently re-open `~/.ssh`; that is the bug this module exists to refuse.
|
|
14
|
+
|
|
15
|
+
Kept dependency-free and pure so both the cage and the broker import it, and so
|
|
16
|
+
the tests can drive it on any platform without a sandbox.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def canonicaliza(ruta):
|
|
23
|
+
"""Absolute real path, resolved through the deepest ancestor that exists.
|
|
24
|
+
|
|
25
|
+
`os.path.realpath` already does this on most inputs, but it stops resolving
|
|
26
|
+
once it meets a missing component; we walk up to the first existing ancestor,
|
|
27
|
+
resolve *that*, then re-attach the missing tail. A symlinked parent with a
|
|
28
|
+
not-yet-created child therefore still lands on the real target.
|
|
29
|
+
"""
|
|
30
|
+
ruta = os.path.abspath(os.path.expanduser(ruta))
|
|
31
|
+
cola = []
|
|
32
|
+
actual = ruta
|
|
33
|
+
while not os.path.exists(actual):
|
|
34
|
+
padre = os.path.dirname(actual)
|
|
35
|
+
if padre == actual:
|
|
36
|
+
break
|
|
37
|
+
cola.append(os.path.basename(actual))
|
|
38
|
+
actual = padre
|
|
39
|
+
base = os.path.realpath(actual)
|
|
40
|
+
for segmento in reversed(cola):
|
|
41
|
+
base = os.path.join(base, segmento)
|
|
42
|
+
return base
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _segmentos(ruta):
|
|
46
|
+
return [s for s in canonicaliza(ruta).split(os.sep) if s]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _dentro_seg(h, p):
|
|
50
|
+
return len(h) >= len(p) and h[: len(p)] == p
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _cubre_seg(a, d):
|
|
54
|
+
return len(a) < len(d) and d[: len(a)] == a
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def dentro_de(hijo, padre):
|
|
58
|
+
"""True when `hijo` is `padre` or lives under it — compared segment-wise so
|
|
59
|
+
`/a/bc` is never judged to be inside `/a/b`."""
|
|
60
|
+
return _dentro_seg(_segmentos(hijo), _segmentos(padre))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def cubre(ancestro, descendiente):
|
|
64
|
+
"""True when `ancestro` is a strict parent of `descendiente` (covers it)."""
|
|
65
|
+
return _cubre_seg(_segmentos(ancestro), _segmentos(descendiente))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def motivo_bloqueo(ruta, bloqueados):
|
|
69
|
+
"""Why `ruta` may not be trusted against a set of forbidden roots, or None.
|
|
70
|
+
|
|
71
|
+
Bidirectional on purpose: a path is refused both when it sits inside a
|
|
72
|
+
forbidden root and when it is an ancestor that would re-open one. `ruta` is
|
|
73
|
+
canonicalized once here and compared segment-wise against every root.
|
|
74
|
+
"""
|
|
75
|
+
r_can = canonicaliza(ruta)
|
|
76
|
+
r_seg = [s for s in r_can.split(os.sep) if s]
|
|
77
|
+
for raiz in bloqueados:
|
|
78
|
+
raiz_seg = _segmentos(raiz)
|
|
79
|
+
if _dentro_seg(r_seg, raiz_seg):
|
|
80
|
+
return f'{r_can} is inside sealed {canonicaliza(raiz)}'
|
|
81
|
+
if _cubre_seg(r_seg, raiz_seg):
|
|
82
|
+
return f'{r_can} covers sealed {canonicaliza(raiz)}'
|
|
83
|
+
return None
|