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,135 @@
|
|
|
1
|
+
import { ActorRole, isoNow, oneOf, randomId, strings, text } from '../protocol.js';
|
|
2
|
+
import { requireChair, requireMember } from './guards.js';
|
|
3
|
+
import { DeliberationState, FLOOR_BASES, FloorRequest, TransitionResult } from './types.js';
|
|
4
|
+
|
|
5
|
+
export function requestFloor(
|
|
6
|
+
state: DeliberationState,
|
|
7
|
+
payload: Record<string, unknown>,
|
|
8
|
+
actor: string,
|
|
9
|
+
role: ActorRole,
|
|
10
|
+
): TransitionResult {
|
|
11
|
+
requireMember(state, actor, role);
|
|
12
|
+
if (state.status !== 'deliberating')
|
|
13
|
+
throw new Error('the floor opens only after the chair synthesis');
|
|
14
|
+
const alreadyUsed = state.floor.replies.filter((reply) => reply.actor === actor).length;
|
|
15
|
+
const alreadyPending = state.floor.requests.some(
|
|
16
|
+
(request) => request.actor === actor && ['pending', 'granted'].includes(request.status),
|
|
17
|
+
);
|
|
18
|
+
if (alreadyPending) throw new Error(`${actor} already has a pending floor request`);
|
|
19
|
+
if (alreadyUsed >= state.brief.maxRebuttals) {
|
|
20
|
+
throw new Error(`${actor} reached this deliberation's rebuttal limit`);
|
|
21
|
+
}
|
|
22
|
+
const request: FloorRequest = {
|
|
23
|
+
id: randomId('floor'),
|
|
24
|
+
actor,
|
|
25
|
+
basis: oneOf(payload.basis, FLOOR_BASES, 'basis'),
|
|
26
|
+
reason: text(payload.reason, 'reason'),
|
|
27
|
+
evidence: strings(payload.evidence, 'evidence', true),
|
|
28
|
+
status: 'pending',
|
|
29
|
+
requestedAt: isoNow(),
|
|
30
|
+
};
|
|
31
|
+
state.floor.requests.push(request);
|
|
32
|
+
return {
|
|
33
|
+
state,
|
|
34
|
+
deliveries: [{ kind: 'committee.floor.requested', to: 'seat', payload: { request } }],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function grantFloor(
|
|
39
|
+
state: DeliberationState,
|
|
40
|
+
payload: Record<string, unknown>,
|
|
41
|
+
actor: string,
|
|
42
|
+
role: ActorRole,
|
|
43
|
+
): TransitionResult {
|
|
44
|
+
requireChair(actor, role);
|
|
45
|
+
if (state.status !== 'deliberating') throw new Error('there is no open deliberation floor');
|
|
46
|
+
if (state.floor.active)
|
|
47
|
+
throw new Error(`the floor already belongs to ${state.floor.active.actor}`);
|
|
48
|
+
const request = pendingRequest(state, text(payload.requestId, 'requestId'));
|
|
49
|
+
request.status = 'granted';
|
|
50
|
+
request.decidedAt = isoNow();
|
|
51
|
+
state.floor.active = { requestId: request.id, actor: request.actor, grantedAt: isoNow() };
|
|
52
|
+
return {
|
|
53
|
+
state,
|
|
54
|
+
deliveries: [
|
|
55
|
+
{
|
|
56
|
+
kind: 'committee.floor.granted',
|
|
57
|
+
to: request.actor,
|
|
58
|
+
payload: { requestId: request.id, basis: request.basis, oneReply: true },
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function denyFloor(
|
|
65
|
+
state: DeliberationState,
|
|
66
|
+
payload: Record<string, unknown>,
|
|
67
|
+
actor: string,
|
|
68
|
+
role: ActorRole,
|
|
69
|
+
): TransitionResult {
|
|
70
|
+
requireChair(actor, role);
|
|
71
|
+
const request = pendingRequest(state, text(payload.requestId, 'requestId'));
|
|
72
|
+
request.status = 'denied';
|
|
73
|
+
request.decidedAt = isoNow();
|
|
74
|
+
request.decisionReason = text(payload.reason, 'reason');
|
|
75
|
+
return {
|
|
76
|
+
state,
|
|
77
|
+
deliveries: [
|
|
78
|
+
{
|
|
79
|
+
kind: 'committee.floor.denied',
|
|
80
|
+
to: request.actor,
|
|
81
|
+
payload: { requestId: request.id, reason: request.decisionReason },
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function replyOnFloor(
|
|
88
|
+
state: DeliberationState,
|
|
89
|
+
payload: Record<string, unknown>,
|
|
90
|
+
actor: string,
|
|
91
|
+
role: ActorRole,
|
|
92
|
+
): TransitionResult {
|
|
93
|
+
requireMember(state, actor, role);
|
|
94
|
+
if (!state.floor.active || state.floor.active.actor !== actor) {
|
|
95
|
+
throw new Error('the chair has not granted this agent the floor');
|
|
96
|
+
}
|
|
97
|
+
const request = state.floor.requests.find((item) => item.id === state.floor.active?.requestId);
|
|
98
|
+
if (!request || request.status !== 'granted')
|
|
99
|
+
throw new Error('the floor grant is no longer valid');
|
|
100
|
+
const reply = {
|
|
101
|
+
requestId: request.id,
|
|
102
|
+
actor,
|
|
103
|
+
claim: text(payload.claim, 'claim'),
|
|
104
|
+
evidence: strings(payload.evidence, 'evidence', true),
|
|
105
|
+
consequence: text(payload.consequence, 'consequence'),
|
|
106
|
+
repliedAt: isoNow(),
|
|
107
|
+
};
|
|
108
|
+
state.floor.replies.push(reply);
|
|
109
|
+
request.status = 'used';
|
|
110
|
+
state.floor.active = null;
|
|
111
|
+
const heardBy = state.brief.participants.filter((participant) => participant !== actor);
|
|
112
|
+
return {
|
|
113
|
+
state,
|
|
114
|
+
deliveries: [
|
|
115
|
+
{ kind: 'committee.reply.received', to: 'seat', payload: { reply } },
|
|
116
|
+
...heardBy.map((to) => ({
|
|
117
|
+
kind: 'committee.reply.heard',
|
|
118
|
+
to,
|
|
119
|
+
payload: {
|
|
120
|
+
reply,
|
|
121
|
+
speaker: actor,
|
|
122
|
+
note: 'This was a chair-granted intervention. Request the floor only if you can add new evidence, a contradiction, a material risk or a dependency.',
|
|
123
|
+
},
|
|
124
|
+
})),
|
|
125
|
+
],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function pendingRequest(state: DeliberationState, requestId: string): FloorRequest {
|
|
130
|
+
const request = state.floor.requests.find(
|
|
131
|
+
(item) => item.id === requestId && item.status === 'pending',
|
|
132
|
+
);
|
|
133
|
+
if (!request) throw new Error('that pending floor request does not exist');
|
|
134
|
+
return request;
|
|
135
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ActorRole } from '../protocol.js';
|
|
2
|
+
import { ActorDirectory, Decision, DeliberationState, TERMINAL_STATUSES } from './types.js';
|
|
3
|
+
|
|
4
|
+
export function requireChair(actor: string, role: ActorRole): void {
|
|
5
|
+
if (role !== 'chair' || actor !== 'seat') throw new Error('only the city chair can do that');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function requireMember(state: DeliberationState, actor: string, role: ActorRole): void {
|
|
9
|
+
if (role !== 'member' || !state.brief.participants.includes(actor)) {
|
|
10
|
+
throw new Error(`${actor} is not a selected member of this deliberation`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function requireOpen(state: DeliberationState): void {
|
|
15
|
+
if (TERMINAL_STATUSES.has(state.status)) {
|
|
16
|
+
throw new Error(`deliberation is ${state.status} and immutable`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function currentDecision(state: DeliberationState): Decision {
|
|
21
|
+
const decision = state.decisions.at(-1);
|
|
22
|
+
if (!decision) throw new Error('this deliberation has no current decision');
|
|
23
|
+
return decision;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function requireKnownVerifier(verifier: string, actors: ActorDirectory): void {
|
|
27
|
+
if (verifier !== 'seat' && actors[verifier]?.role !== 'member') {
|
|
28
|
+
throw new Error(`${verifier} is not an agent in this city`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { DecisionHistory, DeliberationState } from './types.js';
|
|
2
|
+
|
|
3
|
+
const HISTORY_LIMIT = 8;
|
|
4
|
+
|
|
5
|
+
/** Concise chair-only memory. Frequency is surfaced, never treated as a verdict. */
|
|
6
|
+
export function decisionHistory(states: DeliberationState[], current = ''): DecisionHistory {
|
|
7
|
+
const records = states
|
|
8
|
+
.filter((state) => state.id !== current)
|
|
9
|
+
.flatMap((state) =>
|
|
10
|
+
state.decisions.map((decision) => ({
|
|
11
|
+
deliberation: state.id,
|
|
12
|
+
question: state.brief.question,
|
|
13
|
+
outcome: decision.outcome,
|
|
14
|
+
decisiveContributors: decision.decisiveContributors || [],
|
|
15
|
+
verification: decision.verification?.result || 'pending',
|
|
16
|
+
decidedAt: decision.decidedAt,
|
|
17
|
+
reopenIf: decision.reopenIf,
|
|
18
|
+
})),
|
|
19
|
+
)
|
|
20
|
+
.sort((left, right) => right.decidedAt.localeCompare(left.decidedAt));
|
|
21
|
+
|
|
22
|
+
const counts = new Map<string, number>();
|
|
23
|
+
for (const record of records) {
|
|
24
|
+
for (const actor of new Set(record.decisiveContributors)) {
|
|
25
|
+
counts.set(actor, (counts.get(actor) || 0) + 1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const contributorCounts = [...counts]
|
|
29
|
+
.map(([actor, decisions]) => ({ actor, decisions }))
|
|
30
|
+
.sort(
|
|
31
|
+
(left, right) => right.decisions - left.decisions || left.actor.localeCompare(right.actor),
|
|
32
|
+
);
|
|
33
|
+
return {
|
|
34
|
+
recent: records.slice(0, HISTORY_LIMIT),
|
|
35
|
+
contributorCounts,
|
|
36
|
+
note: 'Repeated influence is a review signal, not proof of capture. Re-read dissent and reopen conditions before deciding.',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { writeFileSync, renameSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { DeliberationState } from './types.js';
|
|
4
|
+
|
|
5
|
+
export function renderAct(state: DeliberationState, directory: string): void {
|
|
6
|
+
const lines = [
|
|
7
|
+
`# ${state.brief.question}`,
|
|
8
|
+
'',
|
|
9
|
+
`- **ID:** \`${state.id}\``,
|
|
10
|
+
`- **Status:** ${state.status}`,
|
|
11
|
+
`- **Chair:** ${state.city.address}#seat`,
|
|
12
|
+
`- **Authority:** ${state.brief.authority}`,
|
|
13
|
+
`- **Desired outcome:** ${state.brief.desiredOutcome}`,
|
|
14
|
+
'',
|
|
15
|
+
'## Brief',
|
|
16
|
+
'',
|
|
17
|
+
state.brief.context || '_No extra context._',
|
|
18
|
+
'',
|
|
19
|
+
'### Constraints',
|
|
20
|
+
'',
|
|
21
|
+
...(state.brief.constraints.length
|
|
22
|
+
? state.brief.constraints.map((x) => `- ${x}`)
|
|
23
|
+
: ['- None stated.']),
|
|
24
|
+
'',
|
|
25
|
+
'### Definition of done',
|
|
26
|
+
'',
|
|
27
|
+
...state.brief.definitionOfDone.map((x) => `- ${x}`),
|
|
28
|
+
'',
|
|
29
|
+
'## Participants',
|
|
30
|
+
'',
|
|
31
|
+
...state.brief.participants.map(
|
|
32
|
+
(actor) =>
|
|
33
|
+
`- \`${actor}\` (${state.participantRepos[actor]}) · role ${state.participantRoles?.[actor] || 'blank'} — ${state.positions[actor] ? 'position received' : 'pending'}`,
|
|
34
|
+
),
|
|
35
|
+
'',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// Keep the initial-position barrier visible in the durable human record too.
|
|
39
|
+
if (state.status !== 'collecting') appendPositions(lines, state);
|
|
40
|
+
appendDecision(lines, state);
|
|
41
|
+
appendClosure(lines, state);
|
|
42
|
+
|
|
43
|
+
const path = join(directory, 'ACT.md');
|
|
44
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
45
|
+
writeFileSync(tmp, lines.join('\n') + '\n', { mode: 0o600 });
|
|
46
|
+
renameSync(tmp, path);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function appendPositions(lines: string[], state: DeliberationState): void {
|
|
50
|
+
lines.push('## Independent positions', '');
|
|
51
|
+
for (const actor of state.brief.participants) {
|
|
52
|
+
const p = state.positions[actor];
|
|
53
|
+
if (!p) continue;
|
|
54
|
+
lines.push(
|
|
55
|
+
`### ${actor} — ${p.stance}`,
|
|
56
|
+
'',
|
|
57
|
+
p.recommendation,
|
|
58
|
+
'',
|
|
59
|
+
...p.evidence.map((e) => `- Evidence: ${e}`),
|
|
60
|
+
`- Expected impact: ${p.expectedImpact || 'not quantified'}`,
|
|
61
|
+
`- Visible: ${p.visibleWhen || 'unknown'}`,
|
|
62
|
+
`- Would withdraw if: ${p.withdrawIf || 'not stated'}`,
|
|
63
|
+
'',
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function appendDecision(lines: string[], state: DeliberationState): void {
|
|
69
|
+
const decision = state.decisions.at(-1);
|
|
70
|
+
if (!decision) return;
|
|
71
|
+
lines.push(
|
|
72
|
+
'## Decision',
|
|
73
|
+
'',
|
|
74
|
+
decision.outcome,
|
|
75
|
+
'',
|
|
76
|
+
`- Rationale: ${decision.rationale}`,
|
|
77
|
+
`- Owner: ${decision.owner}`,
|
|
78
|
+
`- Executor: ${decision.executor}`,
|
|
79
|
+
`- Verifier: ${decision.verifier}`,
|
|
80
|
+
`- Decisive contributors: ${(decision.decisiveContributors || []).join(', ') || 'not recorded'}`,
|
|
81
|
+
...decision.dissent.map((x) => `- Dissent preserved: ${x}`),
|
|
82
|
+
...decision.reopenIf.map((x) => `- Reopen if: ${x}`),
|
|
83
|
+
'',
|
|
84
|
+
);
|
|
85
|
+
if (!decision.verification) return;
|
|
86
|
+
lines.push(
|
|
87
|
+
'## Verification',
|
|
88
|
+
'',
|
|
89
|
+
`**${decision.verification.result.toUpperCase()}** by ${decision.verification.verifiedBy}.`,
|
|
90
|
+
'',
|
|
91
|
+
...decision.verification.evidence.map((x) => `- ${x}`),
|
|
92
|
+
'',
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function appendClosure(lines: string[], state: DeliberationState): void {
|
|
97
|
+
if (!state.closure) return;
|
|
98
|
+
lines.push('## Closure', '', state.closure.summary, '');
|
|
99
|
+
for (const x of state.closure.learnings) lines.push(`- Learning: ${x}`);
|
|
100
|
+
for (const x of state.closure.followups) lines.push(`- Follow-up: ${x}`);
|
|
101
|
+
lines.push('');
|
|
102
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ActorRole, asObject } from '../protocol.js';
|
|
2
|
+
import { openDeliberation, publishSynthesis, submitPosition } from './collection.js';
|
|
3
|
+
import { cancelDeliberation, closeDeliberation, decide, replan, verify } from './decision.js';
|
|
4
|
+
import { denyFloor, grantFloor, replyOnFloor, requestFloor } from './floor.js';
|
|
5
|
+
import { requireOpen } from './guards.js';
|
|
6
|
+
import { decisionHistory } from './history.js';
|
|
7
|
+
import { CommitteeFiles } from './storage.js';
|
|
8
|
+
import { ActorDirectory, CityIdentity, TransitionResult } from './types.js';
|
|
9
|
+
import { committeeView } from './view.js';
|
|
10
|
+
|
|
11
|
+
interface CommitteeServiceOptions {
|
|
12
|
+
files: CommitteeFiles;
|
|
13
|
+
city: CityIdentity;
|
|
14
|
+
actors: ActorDirectory;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function committeeService({ files, city, actors }: CommitteeServiceOptions) {
|
|
18
|
+
const transition = (
|
|
19
|
+
command: string,
|
|
20
|
+
thread: string | undefined,
|
|
21
|
+
value: unknown,
|
|
22
|
+
actor: string,
|
|
23
|
+
role: ActorRole,
|
|
24
|
+
): TransitionResult => {
|
|
25
|
+
const payload = asObject(value);
|
|
26
|
+
let result: TransitionResult;
|
|
27
|
+
if (command === 'committee.open') {
|
|
28
|
+
result = openDeliberation(payload, actor, role, city, actors);
|
|
29
|
+
} else {
|
|
30
|
+
if (!thread) throw new Error('thread is required');
|
|
31
|
+
const state = files.load(thread);
|
|
32
|
+
requireOpen(state);
|
|
33
|
+
result = runTransition(command, state, payload, actor, role, actors);
|
|
34
|
+
}
|
|
35
|
+
files.save(result.state, { type: command, actor, role, payload });
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
transition,
|
|
41
|
+
list: () => files.list(),
|
|
42
|
+
history: (current = '') => decisionHistory(files.list(), current),
|
|
43
|
+
view: (thread: string, actor: string, role: ActorRole) => {
|
|
44
|
+
const state = files.load(thread);
|
|
45
|
+
const history = role === 'chair' ? decisionHistory(files.list(), thread) : undefined;
|
|
46
|
+
return committeeView(state, actor, role, history);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function runTransition(
|
|
52
|
+
command: string,
|
|
53
|
+
state: ReturnType<CommitteeFiles['load']>,
|
|
54
|
+
payload: Record<string, unknown>,
|
|
55
|
+
actor: string,
|
|
56
|
+
role: ActorRole,
|
|
57
|
+
actors: ActorDirectory,
|
|
58
|
+
): TransitionResult {
|
|
59
|
+
const common = [state, payload, actor, role] as const;
|
|
60
|
+
if (command === 'committee.respond') return submitPosition(...common);
|
|
61
|
+
if (command === 'committee.synthesize') return publishSynthesis(...common);
|
|
62
|
+
if (command === 'committee.floor.request') return requestFloor(...common);
|
|
63
|
+
if (command === 'committee.floor.grant') return grantFloor(...common);
|
|
64
|
+
if (command === 'committee.floor.deny') return denyFloor(...common);
|
|
65
|
+
if (command === 'committee.reply') return replyOnFloor(...common);
|
|
66
|
+
if (command === 'committee.decide') return decide(...common, actors);
|
|
67
|
+
if (command === 'committee.verify') return verify(state, payload, actor);
|
|
68
|
+
if (command === 'committee.replan') return replan(...common);
|
|
69
|
+
if (command === 'committee.close') return closeDeliberation(...common);
|
|
70
|
+
if (command === 'committee.cancel') return cancelDeliberation(...common);
|
|
71
|
+
throw new Error(`unknown committee transition: ${command}`);
|
|
72
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendFileSync,
|
|
3
|
+
chmodSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
renameSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
import { ActorRole, THREAD_RE, isoNow } from '../protocol.js';
|
|
13
|
+
import { renderAct } from './render.js';
|
|
14
|
+
import { CommitteeEvent, DeliberationState } from './types.js';
|
|
15
|
+
|
|
16
|
+
export interface CommitteeFiles {
|
|
17
|
+
root: string;
|
|
18
|
+
directory(id: string): string;
|
|
19
|
+
load(id: string): DeliberationState;
|
|
20
|
+
list(): DeliberationState[];
|
|
21
|
+
save(
|
|
22
|
+
state: DeliberationState,
|
|
23
|
+
event: { type: string; actor: string; role: ActorRole; payload: Record<string, unknown> },
|
|
24
|
+
): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function committeeFiles(dataDir: string): CommitteeFiles {
|
|
28
|
+
const root = join(dataDir, 'deliberations');
|
|
29
|
+
let counter = 0;
|
|
30
|
+
mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
31
|
+
|
|
32
|
+
const directory = (id: string): string => {
|
|
33
|
+
if (!THREAD_RE.test(id)) throw new Error('invalid deliberation id');
|
|
34
|
+
return join(root, id);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const load = (id: string): DeliberationState => {
|
|
38
|
+
const state = JSON.parse(
|
|
39
|
+
readFileSync(join(directory(id), 'state.json'), 'utf8'),
|
|
40
|
+
) as DeliberationState;
|
|
41
|
+
if (state.schema !== 'agents-city/deliberation@1' || state.id !== id) {
|
|
42
|
+
throw new Error(`unreadable deliberation ${id}`);
|
|
43
|
+
}
|
|
44
|
+
return state;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const list = (): DeliberationState[] => {
|
|
48
|
+
let names: string[] = [];
|
|
49
|
+
try {
|
|
50
|
+
names = readdirSync(root).filter((name) => THREAD_RE.test(name));
|
|
51
|
+
} catch {}
|
|
52
|
+
return names
|
|
53
|
+
.map((name) => {
|
|
54
|
+
try {
|
|
55
|
+
return load(name);
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
.filter((state): state is DeliberationState => state !== null)
|
|
61
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const save = (
|
|
65
|
+
state: DeliberationState,
|
|
66
|
+
event: { type: string; actor: string; role: ActorRole; payload: Record<string, unknown> },
|
|
67
|
+
): void => {
|
|
68
|
+
state.updatedAt = isoNow();
|
|
69
|
+
const dir = directory(state.id);
|
|
70
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
71
|
+
const path = join(dir, 'state.json');
|
|
72
|
+
const tmp = `${path}.tmp-${process.pid}-${counter++}`;
|
|
73
|
+
writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
|
|
74
|
+
renameSync(tmp, path);
|
|
75
|
+
try {
|
|
76
|
+
chmodSync(path, 0o600);
|
|
77
|
+
} catch {}
|
|
78
|
+
|
|
79
|
+
const events = join(dir, 'events.jsonl');
|
|
80
|
+
let seq = 1;
|
|
81
|
+
if (existsSync(events)) {
|
|
82
|
+
try {
|
|
83
|
+
seq = readFileSync(events, 'utf8').split('\n').filter(Boolean).length + 1;
|
|
84
|
+
} catch {}
|
|
85
|
+
}
|
|
86
|
+
const record: CommitteeEvent = { seq, at: isoNow(), ...event };
|
|
87
|
+
appendFileSync(events, JSON.stringify(record) + '\n', { mode: 0o600 });
|
|
88
|
+
renderAct(state, dir);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return { root, directory, load, list, save };
|
|
92
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { ActorRole } from '../protocol.js';
|
|
2
|
+
|
|
3
|
+
export const STANCES = ['support', 'oppose', 'conditional', 'abstain'] as const;
|
|
4
|
+
export const FLOOR_BASES = ['new_evidence', 'contradiction', 'risk', 'dependency'] as const;
|
|
5
|
+
export const AUTHORITIES = ['recommend', 'decide', 'execute'] as const;
|
|
6
|
+
export const VERIFY_RESULTS = ['pass', 'fail'] as const;
|
|
7
|
+
export const TERMINAL_STATUSES = new Set(['closed', 'cancelled']);
|
|
8
|
+
|
|
9
|
+
export type DeliberationStatus =
|
|
10
|
+
| 'collecting'
|
|
11
|
+
| 'review'
|
|
12
|
+
| 'deliberating'
|
|
13
|
+
| 'verifying'
|
|
14
|
+
| 'verified'
|
|
15
|
+
| 'verification_failed'
|
|
16
|
+
| 'closed'
|
|
17
|
+
| 'cancelled';
|
|
18
|
+
|
|
19
|
+
export interface CityIdentity {
|
|
20
|
+
id: string;
|
|
21
|
+
address: string;
|
|
22
|
+
name: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface Position {
|
|
26
|
+
stance: (typeof STANCES)[number];
|
|
27
|
+
recommendation: string;
|
|
28
|
+
evidence: string[];
|
|
29
|
+
expectedImpact: string;
|
|
30
|
+
visibleWhen: string;
|
|
31
|
+
withdrawIf: string;
|
|
32
|
+
risks: string[];
|
|
33
|
+
unknowns: string[];
|
|
34
|
+
submittedAt: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface FloorRequest {
|
|
38
|
+
id: string;
|
|
39
|
+
actor: string;
|
|
40
|
+
basis: (typeof FLOOR_BASES)[number];
|
|
41
|
+
reason: string;
|
|
42
|
+
evidence: string[];
|
|
43
|
+
status: 'pending' | 'granted' | 'used' | 'denied';
|
|
44
|
+
requestedAt: string;
|
|
45
|
+
decidedAt?: string;
|
|
46
|
+
decisionReason?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface Decision {
|
|
50
|
+
id: string;
|
|
51
|
+
outcome: string;
|
|
52
|
+
rationale: string;
|
|
53
|
+
owner: string;
|
|
54
|
+
executor: string;
|
|
55
|
+
verifier: string;
|
|
56
|
+
verificationQuestion: string;
|
|
57
|
+
selectedEvidence: string[];
|
|
58
|
+
decisiveContributors: string[];
|
|
59
|
+
rejectedOptions: string[];
|
|
60
|
+
dissent: string[];
|
|
61
|
+
reopenIf: string[];
|
|
62
|
+
decidedAt: string;
|
|
63
|
+
verification?: {
|
|
64
|
+
result: (typeof VERIFY_RESULTS)[number];
|
|
65
|
+
evidence: string[];
|
|
66
|
+
checks: string[];
|
|
67
|
+
residualRisks: string[];
|
|
68
|
+
verifiedBy: string;
|
|
69
|
+
verifiedAt: string;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface DeliberationState {
|
|
74
|
+
schema: 'agents-city/deliberation@1';
|
|
75
|
+
id: string;
|
|
76
|
+
city: CityIdentity;
|
|
77
|
+
parent: string | null;
|
|
78
|
+
status: DeliberationStatus;
|
|
79
|
+
createdAt: string;
|
|
80
|
+
updatedAt: string;
|
|
81
|
+
brief: {
|
|
82
|
+
question: string;
|
|
83
|
+
desiredOutcome: string;
|
|
84
|
+
context: string;
|
|
85
|
+
constraints: string[];
|
|
86
|
+
definitionOfDone: string[];
|
|
87
|
+
authority: (typeof AUTHORITIES)[number];
|
|
88
|
+
participants: string[];
|
|
89
|
+
maxRebuttals: number;
|
|
90
|
+
};
|
|
91
|
+
participantRepos: Record<string, string>;
|
|
92
|
+
/** Professional perspective, distinct from the chair/member authority role. */
|
|
93
|
+
participantRoles?: Record<string, string>;
|
|
94
|
+
positions: Record<string, Position>;
|
|
95
|
+
synthesis: null | {
|
|
96
|
+
summary: string;
|
|
97
|
+
agreements: string[];
|
|
98
|
+
conflicts: string[];
|
|
99
|
+
unknowns: string[];
|
|
100
|
+
missing: string[];
|
|
101
|
+
proceedWithout: string;
|
|
102
|
+
publishedAt: string;
|
|
103
|
+
};
|
|
104
|
+
floor: {
|
|
105
|
+
requests: FloorRequest[];
|
|
106
|
+
active: null | { requestId: string; actor: string; grantedAt: string };
|
|
107
|
+
replies: Array<{
|
|
108
|
+
requestId: string;
|
|
109
|
+
actor: string;
|
|
110
|
+
claim: string;
|
|
111
|
+
evidence: string[];
|
|
112
|
+
consequence: string;
|
|
113
|
+
repliedAt: string;
|
|
114
|
+
}>;
|
|
115
|
+
};
|
|
116
|
+
decisions: Decision[];
|
|
117
|
+
closure: null | {
|
|
118
|
+
summary: string;
|
|
119
|
+
learnings: string[];
|
|
120
|
+
followups: string[];
|
|
121
|
+
closedAt: string;
|
|
122
|
+
};
|
|
123
|
+
progress: { revision: number; failedVerifications: number };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface CommitteeEvent {
|
|
127
|
+
seq: number;
|
|
128
|
+
at: string;
|
|
129
|
+
type: string;
|
|
130
|
+
actor: string;
|
|
131
|
+
role: ActorRole;
|
|
132
|
+
payload: Record<string, unknown>;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface Delivery {
|
|
136
|
+
kind: string;
|
|
137
|
+
to: string;
|
|
138
|
+
payload: Record<string, unknown>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface TransitionResult {
|
|
142
|
+
state: DeliberationState;
|
|
143
|
+
deliveries: Delivery[];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export interface DecisionHistory {
|
|
147
|
+
recent: Array<{
|
|
148
|
+
deliberation: string;
|
|
149
|
+
question: string;
|
|
150
|
+
outcome: string;
|
|
151
|
+
decisiveContributors: string[];
|
|
152
|
+
verification: string;
|
|
153
|
+
decidedAt: string;
|
|
154
|
+
reopenIf: string[];
|
|
155
|
+
}>;
|
|
156
|
+
contributorCounts: Array<{ actor: string; decisions: number }>;
|
|
157
|
+
note: string;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export type ActorDirectory = Record<
|
|
161
|
+
string,
|
|
162
|
+
{ role: ActorRole; repo?: string; operatingRole?: string }
|
|
163
|
+
>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ActorRole } from '../protocol.js';
|
|
2
|
+
import { requireChair, requireMember } from './guards.js';
|
|
3
|
+
import { DecisionHistory, DeliberationState } from './types.js';
|
|
4
|
+
|
|
5
|
+
export function committeeView(
|
|
6
|
+
state: DeliberationState,
|
|
7
|
+
actor: string,
|
|
8
|
+
role: ActorRole,
|
|
9
|
+
history?: DecisionHistory,
|
|
10
|
+
): Record<string, unknown> {
|
|
11
|
+
const progress = {
|
|
12
|
+
status: state.status,
|
|
13
|
+
received: Object.keys(state.positions).length,
|
|
14
|
+
total: state.brief.participants.length,
|
|
15
|
+
missing: state.brief.participants.filter((participant) => !state.positions[participant]),
|
|
16
|
+
pendingFloor: state.floor.requests.filter((request) => request.status === 'pending').length,
|
|
17
|
+
activeFloor: state.floor.active?.actor || null,
|
|
18
|
+
revision: state.progress.revision,
|
|
19
|
+
failedVerifications: state.progress.failedVerifications,
|
|
20
|
+
};
|
|
21
|
+
const base: Record<string, unknown> = {
|
|
22
|
+
schema: state.schema,
|
|
23
|
+
id: state.id,
|
|
24
|
+
city: state.city,
|
|
25
|
+
brief: state.brief,
|
|
26
|
+
participantRepos: state.participantRepos,
|
|
27
|
+
participantRoles: state.participantRoles || {},
|
|
28
|
+
progress,
|
|
29
|
+
};
|
|
30
|
+
if (role === 'member') {
|
|
31
|
+
const selected = state.brief.participants.includes(actor);
|
|
32
|
+
const assignedVerifier = state.decisions.at(-1)?.verifier === actor;
|
|
33
|
+
if (!selected && !assignedVerifier) requireMember(state, actor, role);
|
|
34
|
+
return {
|
|
35
|
+
...base,
|
|
36
|
+
myPosition: state.positions[actor] || null,
|
|
37
|
+
synthesis: state.synthesis,
|
|
38
|
+
myFloorRequests: state.floor.requests.filter((request) => request.actor === actor),
|
|
39
|
+
myReplies: state.floor.replies.filter((reply) => reply.actor === actor),
|
|
40
|
+
decision: state.decisions.at(-1) || null,
|
|
41
|
+
closure: state.closure,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
requireChair(actor, role);
|
|
45
|
+
return {
|
|
46
|
+
...base,
|
|
47
|
+
// The chair sees all positions at once. Early arrivals cannot anchor it.
|
|
48
|
+
positions:
|
|
49
|
+
state.status === 'collecting'
|
|
50
|
+
? Object.fromEntries(
|
|
51
|
+
state.brief.participants.map((participant) => [
|
|
52
|
+
participant,
|
|
53
|
+
state.positions[participant] ? 'received-hidden' : 'pending',
|
|
54
|
+
]),
|
|
55
|
+
)
|
|
56
|
+
: state.positions,
|
|
57
|
+
synthesis: state.synthesis,
|
|
58
|
+
floor: state.floor,
|
|
59
|
+
decisions: state.decisions,
|
|
60
|
+
closure: state.closure,
|
|
61
|
+
history,
|
|
62
|
+
};
|
|
63
|
+
}
|