agentshire 1.0.0
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/CHANGELOG.md +36 -0
- package/LICENSE +21 -0
- package/README.md +437 -0
- package/README.zh-CN.md +441 -0
- package/ROADMAP.md +207 -0
- package/THIRD_PARTY_NOTICES.md +49 -0
- package/VISION.md +101 -0
- package/index.ts +386 -0
- package/openclaw.plugin.json +27 -0
- package/package.json +100 -0
- package/src/bridge/AGENTS.md +136 -0
- package/src/bridge/ActivityStream.ts +184 -0
- package/src/bridge/CitizenManager.ts +280 -0
- package/src/bridge/DirectorBridge.ts +1076 -0
- package/src/bridge/EventTranslator.ts +142 -0
- package/src/bridge/NpcEventQueue.ts +61 -0
- package/src/bridge/ReconnectManager.ts +42 -0
- package/src/bridge/RouteManager.ts +270 -0
- package/src/bridge/StateTracker.ts +100 -0
- package/src/bridge/ToolVfxMapper.ts +94 -0
- package/src/bridge/__tests__/ActivityStream.test.ts +156 -0
- package/src/bridge/__tests__/CitizenManager.test.ts +174 -0
- package/src/bridge/__tests__/EventTranslator.test.ts +292 -0
- package/src/bridge/__tests__/NpcEventQueue.test.ts +92 -0
- package/src/bridge/__tests__/RouteManager.test.ts +106 -0
- package/src/bridge/data/route-config.json +36 -0
- package/src/bridge/data/route-config.ts +19 -0
- package/src/bridge/implicit-chat.ts +192 -0
- package/src/bridge/index.ts +12 -0
- package/src/contracts/agent-state.ts +87 -0
- package/src/contracts/agui.ts +212 -0
- package/src/contracts/chat.ts +67 -0
- package/src/contracts/events.ts +170 -0
- package/src/contracts/index.ts +71 -0
- package/src/contracts/media.ts +77 -0
- package/src/contracts/registry.ts +32 -0
- package/src/plugin/AGENTS.md +192 -0
- package/src/plugin/__tests__/editor-serve-megapack.test.ts +150 -0
- package/src/plugin/__tests__/hook-translator.test.ts +197 -0
- package/src/plugin/auto-config.ts +131 -0
- package/src/plugin/channel.ts +399 -0
- package/src/plugin/chat-asset-resolver.ts +75 -0
- package/src/plugin/chat-session-watcher.ts +98 -0
- package/src/plugin/citizen-agent-manager.ts +178 -0
- package/src/plugin/citizen-chat-router.ts +83 -0
- package/src/plugin/citizen-workshop-manager.ts +96 -0
- package/src/plugin/custom-asset-manager.ts +250 -0
- package/src/plugin/editor-serve.ts +1368 -0
- package/src/plugin/group-discussion.ts +223 -0
- package/src/plugin/hook-translator.ts +202 -0
- package/src/plugin/llm-agent-proxy.ts +193 -0
- package/src/plugin/outbound-adapter.ts +187 -0
- package/src/plugin/paths.ts +33 -0
- package/src/plugin/plan-manager.ts +594 -0
- package/src/plugin/runtime.ts +9 -0
- package/src/plugin/session-history.ts +838 -0
- package/src/plugin/session-log-watcher.ts +221 -0
- package/src/plugin/soul-prompt-template.ts +290 -0
- package/src/plugin/subagent-tracker.ts +205 -0
- package/src/plugin/tools.ts +493 -0
- package/src/plugin/town-session.ts +40 -0
- package/src/plugin/ws-server.ts +680 -0
- package/src/town-souls.ts +134 -0
- package/town-frontend/dist/assets/CustomAssetStore-oi8aIurt.js +2 -0
- package/town-frontend/dist/assets/GLTFLoader-BA5RqSME.js +3804 -0
- package/town-frontend/dist/assets/OrbitControls-ZmySp9sQ.js +2 -0
- package/town-frontend/dist/assets/SettingsPanel-BO52reJe.js +2 -0
- package/town-frontend/dist/assets/SkeletonUtils-BCVmgslc.js +2 -0
- package/town-frontend/dist/assets/SkillLearnCard-Dk38iDpy.js +6 -0
- package/town-frontend/dist/assets/TopicSetupPanel-DLaLHB_Z.js +2 -0
- package/town-frontend/dist/assets/Trap-Bold-CT0JBE39.woff2 +0 -0
- package/town-frontend/dist/assets/Trap-SemiBold-R4_-Ld0j.woff2 +0 -0
- package/town-frontend/dist/assets/WeatherSystem-Cb3BvHes.js +550 -0
- package/town-frontend/dist/assets/avatars/char-female-a.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-female-b.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-female-c.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-female-d.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-female-e.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-female-f.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-male-a.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-male-b.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-male-c.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-male-d.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-male-e.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-male-f.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-beaver.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-bee.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-bunny.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-cat.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-caterpillar.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-chick.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-cow.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-crab.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-deer.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-dog.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-elephant.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-fish.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-fox.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-giraffe.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-hog.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-koala.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-lion.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-monkey.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-panda.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-parrot.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-penguin.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-pig.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-polar.webp +0 -0
- package/town-frontend/dist/assets/avatars/char-pet-tiger.webp +0 -0
- package/town-frontend/dist/assets/character-catalog-DSmLtlNC.js +2 -0
- package/town-frontend/dist/assets/citizenEditor-DubGSJOQ.js +296 -0
- package/town-frontend/dist/assets/command-parser-BUd15Bmv.js +12 -0
- package/town-frontend/dist/assets/editor-B5QO0OtX.js +258 -0
- package/town-frontend/dist/assets/editor-Bk8g1NCD.css +1 -0
- package/town-frontend/dist/assets/index-BWfrufil.js +2 -0
- package/town-frontend/dist/assets/index-faS20RJk.js +6 -0
- package/town-frontend/dist/assets/logo-DJI6EtST.png +0 -0
- package/town-frontend/dist/assets/logo-title-AdKPZX5E.png +0 -0
- package/town-frontend/dist/assets/main-CqsN43aT.js +224 -0
- package/town-frontend/dist/assets/main-D7neuy3w.css +1 -0
- package/town-frontend/dist/assets/models/buildings/base.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/base.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/bench.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/bench.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/box_A.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/box_A.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/box_B.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/box_B.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_A.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_A.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_A_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_A_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_B.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_B.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_B_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_B_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_C.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_C.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_C_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_C_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_D.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_D.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_D_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_D_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_E.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_E.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_E_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_E_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_F.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_F.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_F_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_F_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_G.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_G.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_G_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_G_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_H.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_H.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/building_H_withoutBase.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/building_H_withoutBase.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/bush.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/bush.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/car_hatchback.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/car_hatchback.gltf +442 -0
- package/town-frontend/dist/assets/models/buildings/car_police.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/car_police.gltf +442 -0
- package/town-frontend/dist/assets/models/buildings/car_sedan.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/car_sedan.gltf +442 -0
- package/town-frontend/dist/assets/models/buildings/car_stationwagon.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/car_stationwagon.gltf +442 -0
- package/town-frontend/dist/assets/models/buildings/car_taxi.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/car_taxi.gltf +442 -0
- package/town-frontend/dist/assets/models/buildings/citybits_texture.png +0 -0
- package/town-frontend/dist/assets/models/buildings/dumpster.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/dumpster.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/firehydrant.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/firehydrant.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/road_corner.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/road_corner.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/road_corner_curved.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/road_corner_curved.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/road_junction.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/road_junction.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/road_straight.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/road_straight.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/road_straight_crossing.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/road_straight_crossing.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/road_tsplit.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/road_tsplit.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/streetlight.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/streetlight.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/trafficlight_A.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/trafficlight_A.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/trafficlight_B.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/trafficlight_B.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/trafficlight_C.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/trafficlight_C.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/trash_A.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/trash_A.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/trash_B.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/trash_B.gltf +136 -0
- package/town-frontend/dist/assets/models/buildings/watertower.bin +0 -0
- package/town-frontend/dist/assets/models/buildings/watertower.gltf +136 -0
- package/town-frontend/dist/assets/models/characters/Textures/colormap.png +0 -0
- package/town-frontend/dist/assets/models/characters/character-female-a.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-female-b.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-female-c.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-female-d.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-female-e.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-female-f.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-male-a.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-male-b.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-male-c.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-male-d.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-male-e.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-male-f.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-beaver.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-bee.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-bunny.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-cat.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-caterpillar.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-chick.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-cow.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-crab.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-deer.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-dog.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-elephant.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-fish.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-fox.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-giraffe.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-hog.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-koala.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-lion.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-monkey.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-panda.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-parrot.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-penguin.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-pig.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-polar.glb +0 -0
- package/town-frontend/dist/assets/models/characters/character-pet-tiger.glb +0 -0
- package/town-frontend/dist/assets/models/characters/colormap.png +0 -0
- package/town-frontend/dist/assets/models/furniture/armchair.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/armchair.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/armchair_pillows.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/armchair_pillows.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/bed_double_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/bed_double_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/bed_double_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/bed_double_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/bed_single_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/bed_single_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/bed_single_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/bed_single_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/book_set.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/book_set.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/book_single.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/book_single.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_medium.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_medium.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_medium_decorated.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_medium_decorated.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_small.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_small.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_small_decorated.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cabinet_small_decorated.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cactus_medium_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cactus_medium_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cactus_medium_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cactus_medium_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cactus_small_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cactus_small_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/cactus_small_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/cactus_small_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_A_wood.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_A_wood.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_B_wood.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_B_wood.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_C.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_C.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_stool.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_stool.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/chair_stool_wood.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/chair_stool_wood.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/couch.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/couch.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/couch_pillows.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/couch_pillows.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/furniturebits_texture.png +0 -0
- package/town-frontend/dist/assets/models/furniture/lamp_standing.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/lamp_standing.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/lamp_table.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/lamp_table.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_large_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_large_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_large_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_large_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_medium.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_medium.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_small_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_small_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_small_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_small_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_small_C.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_small_C.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_standing_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_standing_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_standing_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pictureframe_standing_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pillow_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pillow_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/pillow_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/pillow_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/rug_oval_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/rug_oval_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/rug_oval_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/rug_oval_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_stripes_A.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_stripes_A.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_stripes_B.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/rug_rectangle_stripes_B.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/shelf_A_big.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/shelf_A_big.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/shelf_A_small.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/shelf_A_small.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_large.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_large.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_large_decorated.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_large_decorated.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_small.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_small.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_small_decorated.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/shelf_B_small_decorated.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/table_low.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/table_low.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/table_medium.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/table_medium.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/table_medium_long.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/table_medium_long.gltf +136 -0
- package/town-frontend/dist/assets/models/furniture/table_small.bin +0 -0
- package/town-frontend/dist/assets/models/furniture/table_small.gltf +136 -0
- package/town-frontend/dist/assets/models/props/bench.bin +0 -0
- package/town-frontend/dist/assets/models/props/bench.gltf +136 -0
- package/town-frontend/dist/assets/models/props/bush.bin +0 -0
- package/town-frontend/dist/assets/models/props/bush.gltf +136 -0
- package/town-frontend/dist/assets/models/props/capybara.glb +0 -0
- package/town-frontend/dist/assets/models/props/car_hatchback.bin +0 -0
- package/town-frontend/dist/assets/models/props/car_hatchback.gltf +442 -0
- package/town-frontend/dist/assets/models/props/car_sedan.bin +0 -0
- package/town-frontend/dist/assets/models/props/car_sedan.gltf +442 -0
- package/town-frontend/dist/assets/models/props/car_taxi.bin +0 -0
- package/town-frontend/dist/assets/models/props/car_taxi.gltf +442 -0
- package/town-frontend/dist/assets/models/props/citybits_texture.png +0 -0
- package/town-frontend/dist/assets/models/props/dumpster.bin +0 -0
- package/town-frontend/dist/assets/models/props/dumpster.gltf +136 -0
- package/town-frontend/dist/assets/models/props/firehydrant.bin +0 -0
- package/town-frontend/dist/assets/models/props/firehydrant.gltf +136 -0
- package/town-frontend/dist/assets/models/props/streetlight.bin +0 -0
- package/town-frontend/dist/assets/models/props/streetlight.gltf +136 -0
- package/town-frontend/dist/assets/models/props/trafficlight_A.bin +0 -0
- package/town-frontend/dist/assets/models/props/trafficlight_A.gltf +136 -0
- package/town-frontend/dist/assets/models/props/trash_A.bin +0 -0
- package/town-frontend/dist/assets/models/props/trash_A.gltf +136 -0
- package/town-frontend/dist/assets/models/props/watertower.bin +0 -0
- package/town-frontend/dist/assets/models/props/watertower.gltf +136 -0
- package/town-frontend/dist/assets/models/stage-deco/Flowers_1_D.glb +0 -0
- package/town-frontend/dist/assets/models/stage-deco/Flowers_2_B.glb +0 -0
- package/town-frontend/dist/assets/models/stage-deco/Grass_A_1.glb +0 -0
- package/town-frontend/dist/assets/models/stage-deco/Park_GrassHill_A.glb +0 -0
- package/town-frontend/dist/assets/models/stage-deco/Pebles_1_A_2.glb +0 -0
- package/town-frontend/dist/assets/music/bgm_day.mp3 +0 -0
- package/town-frontend/dist/assets/music/bgm_dusk.mp3 +0 -0
- package/town-frontend/dist/assets/music/bgm_night.mp3 +0 -0
- package/town-frontend/dist/assets/music/bgm_work.mp3 +0 -0
- package/town-frontend/dist/assets/office-whiteboard-idle-CyEwBrq_.webp +0 -0
- package/town-frontend/dist/assets/preview-B6hYEQij.js +2 -0
- package/town-frontend/dist/assets/town-BKesnERP.js +8888 -0
- package/town-frontend/dist/assets/town-BnswKsjF.css +1 -0
- package/town-frontend/dist/citizen-editor.html +166 -0
- package/town-frontend/dist/editor.html +227 -0
- package/town-frontend/dist/index.html +22 -0
- package/town-frontend/dist/preview.html +56 -0
- package/town-frontend/dist/town.html +165 -0
- package/town-frontend/dist/viewer.html +91 -0
- package/town-souls/CHEN.md +130 -0
- package/town-souls/CHENGZI.md +92 -0
- package/town-souls/CITIZEN_tpl.md +16 -0
- package/town-souls/DIANDIAN.md +92 -0
- package/town-souls/HAITANG.md +94 -0
- package/town-souls/QIQI.md +119 -0
- package/town-souls/SOUL.md +141 -0
- package/town-souls/SOUL_tpl.md +135 -0
- package/town-souls/XIAOLIE.md +107 -0
- package/town-souls/YAN.md +107 -0
- package/town-workspace/IDENTITY.md +5 -0
- package/town-workspace/SOUL.md +141 -0
- package/town-workspace/project-workflow.md +81 -0
- package/town-workspace/town-defaults.json +92 -0
- package/town-workspace/town-guide.md +45 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// @desc Lightweight AgentEvent → GameEvent translator used as fallback by DirectorBridge
|
|
2
|
+
import type { AgentEvent } from '../contracts/events.js'
|
|
3
|
+
import type { StateTracker } from './StateTracker.js'
|
|
4
|
+
import type { GameEvent } from '../../town-frontend/src/data/GameProtocol.js'
|
|
5
|
+
|
|
6
|
+
export type { GameEvent } from '../../town-frontend/src/data/GameProtocol.js'
|
|
7
|
+
|
|
8
|
+
const FILE_TOOLS = new Set(['read_file', 'write_file', 'edit_file'])
|
|
9
|
+
|
|
10
|
+
function extractFilePath(toolName: string, input: Record<string, unknown>): string | null {
|
|
11
|
+
if (FILE_TOOLS.has(toolName)) return (input.path ?? input.file) as string | null
|
|
12
|
+
if (toolName === 'bash') {
|
|
13
|
+
const cmd = String(input.command ?? '')
|
|
14
|
+
const m = cmd.match(/(?:cat|head|tail|vi|vim|nano|code)\s+["']?([^\s"']+)/)
|
|
15
|
+
return m ? m[1] : null
|
|
16
|
+
}
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Translates AgentEvent (from the agent protocol) into GameEvent[] (consumed by the 3D frontend). Used by DirectorBridge as a default/fallback handler for event types it doesn't process directly. */
|
|
21
|
+
export class EventTranslator {
|
|
22
|
+
private tracker: StateTracker
|
|
23
|
+
|
|
24
|
+
constructor(tracker: StateTracker) {
|
|
25
|
+
this.tracker = tracker
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Main entry: switch on event type and return corresponding GameEvents */
|
|
29
|
+
translate(event: AgentEvent, contextNpcId?: string): GameEvent[] {
|
|
30
|
+
switch (event.type) {
|
|
31
|
+
case 'system':
|
|
32
|
+
return this.handleSystem(event)
|
|
33
|
+
case 'sub_agent':
|
|
34
|
+
return this.handleSubAgent(event)
|
|
35
|
+
case 'text_delta':
|
|
36
|
+
return [{ type: 'dialog_message', npcId: contextNpcId ?? this.tracker.stewardNpcId, text: event.delta, isStreaming: true }]
|
|
37
|
+
case 'text':
|
|
38
|
+
return [{ type: 'dialog_message', npcId: contextNpcId ?? this.tracker.stewardNpcId, text: event.content, isStreaming: false }]
|
|
39
|
+
case 'tool_use':
|
|
40
|
+
return this.handleToolUse(event, contextNpcId)
|
|
41
|
+
case 'tool_result':
|
|
42
|
+
return this.handleToolResult(event, contextNpcId)
|
|
43
|
+
case 'thinking_delta':
|
|
44
|
+
return contextNpcId
|
|
45
|
+
? [{ type: 'npc_phase', npcId: contextNpcId, phase: 'thinking' }]
|
|
46
|
+
: [{ type: 'npc_phase', npcId: this.tracker.stewardNpcId, phase: 'thinking' }]
|
|
47
|
+
case 'turn_end':
|
|
48
|
+
return contextNpcId
|
|
49
|
+
? [{ type: 'npc_phase', npcId: contextNpcId, phase: 'idle' }, { type: 'workstation_screen', stationId: '', state: { mode: 'off' } }]
|
|
50
|
+
: []
|
|
51
|
+
case 'bus_message': {
|
|
52
|
+
const fromNpc = this.tracker.resolveNpcId(event.from)
|
|
53
|
+
if (fromNpc) return [{ type: 'dialog_message', npcId: fromNpc, text: event.summary, isStreaming: false }]
|
|
54
|
+
return []
|
|
55
|
+
}
|
|
56
|
+
case 'world_control': {
|
|
57
|
+
if (event.target === 'time') {
|
|
58
|
+
return [{ type: 'set_time' as const, action: event.action, hour: event.hour }]
|
|
59
|
+
}
|
|
60
|
+
if (event.target === 'weather') {
|
|
61
|
+
return [{ type: 'set_weather' as const, action: event.action, weather: event.weather }]
|
|
62
|
+
}
|
|
63
|
+
return []
|
|
64
|
+
}
|
|
65
|
+
case 'error':
|
|
66
|
+
return contextNpcId
|
|
67
|
+
? [{ type: 'npc_phase', npcId: contextNpcId, phase: 'error' }, { type: 'npc_emote', npcId: contextNpcId, emote: 'frustrated' }]
|
|
68
|
+
: []
|
|
69
|
+
default:
|
|
70
|
+
return []
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private handleSystem(event: Extract<AgentEvent, { type: 'system' }>): GameEvent[] {
|
|
75
|
+
if (event.subtype === 'init') {
|
|
76
|
+
console.log('[EventTranslator] handleSystem init, sending world_init (NPCs already spawned by MainScene from town-defaults.json)')
|
|
77
|
+
return [
|
|
78
|
+
{ type: 'world_init', config: { townName: '夏尔', stewardName: 'OpenClaw', citizenCount: 5 } },
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
return []
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private handleSubAgent(event: Extract<AgentEvent, { type: 'sub_agent' }>): GameEvent[] {
|
|
85
|
+
const { subtype, agentId } = event
|
|
86
|
+
|
|
87
|
+
if (subtype === 'started') {
|
|
88
|
+
const displayName = event.displayName ?? agentId
|
|
89
|
+
const npcId = agentId.replace(/^agent_/, '')
|
|
90
|
+
this.tracker.registerMapping(agentId, npcId)
|
|
91
|
+
const stationId = this.tracker.allocateStation()
|
|
92
|
+
|
|
93
|
+
const events: GameEvent[] = [
|
|
94
|
+
{ type: 'npc_spawn', npcId, name: displayName, role: 'programming', category: 'citizen', task: event.task },
|
|
95
|
+
]
|
|
96
|
+
if (stationId) {
|
|
97
|
+
this.tracker.setStationForNpc(npcId, stationId)
|
|
98
|
+
events.push({ type: 'workstation_assign', npcId, stationId })
|
|
99
|
+
}
|
|
100
|
+
return events
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (subtype === 'done') {
|
|
104
|
+
const npcId = this.tracker.resolveNpcId(agentId)
|
|
105
|
+
if (!npcId) return []
|
|
106
|
+
const status = event.status as string
|
|
107
|
+
const isError = status === 'failed'
|
|
108
|
+
this.tracker.removeMapping(agentId)
|
|
109
|
+
return [
|
|
110
|
+
{ type: 'npc_phase', npcId, phase: isError ? 'error' : 'done' },
|
|
111
|
+
{ type: 'npc_glow', npcId, color: isError ? 'red' : 'green' },
|
|
112
|
+
{ type: 'fx', effect: isError ? 'error_sparks' : 'completion_stars', params: { npcId } },
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (subtype === 'progress') {
|
|
117
|
+
const npcId = this.tracker.resolveNpcId(agentId)
|
|
118
|
+
if (!npcId) return []
|
|
119
|
+
return this.translate(event.event, npcId)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return []
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private handleToolUse(event: Extract<AgentEvent, { type: 'tool_use' }>, contextNpcId?: string): GameEvent[] {
|
|
126
|
+
const npcId = contextNpcId ?? this.tracker.stewardNpcId
|
|
127
|
+
const filePath = extractFilePath(event.name, event.input)
|
|
128
|
+
const events: GameEvent[] = [{ type: 'npc_phase', npcId, phase: 'working' }]
|
|
129
|
+
if (filePath) {
|
|
130
|
+
const fileName = filePath.split('/').pop() ?? filePath
|
|
131
|
+
events.push({ type: 'workstation_screen', stationId: '', state: { mode: 'coding', fileName } })
|
|
132
|
+
}
|
|
133
|
+
return events
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private handleToolResult(event: Extract<AgentEvent, { type: 'tool_result' }>, contextNpcId?: string): GameEvent[] {
|
|
137
|
+
if (event.meta?.filePath) {
|
|
138
|
+
return [{ type: 'workstation_screen', stationId: '', state: { mode: 'done' } }]
|
|
139
|
+
}
|
|
140
|
+
return []
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// @desc Event queue that protects dialog bubbles from being overridden by rapid phase changes
|
|
2
|
+
import type { GameEvent } from '../../town-frontend/src/data/GameProtocol.js'
|
|
3
|
+
|
|
4
|
+
/** Calculate how long a dialog bubble should stay visible based on text length */
|
|
5
|
+
export function calcDialogDuration(textLength: number): number {
|
|
6
|
+
return Math.min(8000, Math.max(1500, textLength * 120))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Queue that serializes dialog and phase events per NPC, ensuring dialogs display long enough before phase changes take effect */
|
|
10
|
+
export class NpcEventQueue {
|
|
11
|
+
private pendingPhase: Array<{ events: GameEvent[] }> = []
|
|
12
|
+
private dialogProtected = false
|
|
13
|
+
private dialogTimer: ReturnType<typeof setTimeout> | null = null
|
|
14
|
+
private dialogTextLength = 0
|
|
15
|
+
private emitFn: (events: GameEvent[]) => void
|
|
16
|
+
|
|
17
|
+
constructor(emitFn: (events: GameEvent[]) => void) {
|
|
18
|
+
this.emitFn = emitFn
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Emit dialog events immediately and protect the bubble for a duration proportional to text length */
|
|
22
|
+
enqueueDialog(events: GameEvent[], textLength: number): void {
|
|
23
|
+
this.emitFn(events)
|
|
24
|
+
|
|
25
|
+
this.dialogTextLength += textLength
|
|
26
|
+
this.dialogProtected = true
|
|
27
|
+
|
|
28
|
+
if (this.dialogTimer) clearTimeout(this.dialogTimer)
|
|
29
|
+
const duration = calcDialogDuration(this.dialogTextLength)
|
|
30
|
+
this.dialogTimer = setTimeout(() => {
|
|
31
|
+
this.dialogProtected = false
|
|
32
|
+
this.dialogTimer = null
|
|
33
|
+
this.dialogTextLength = 0
|
|
34
|
+
this.drainPending()
|
|
35
|
+
}, duration)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Emit phase events immediately if no dialog is protected, otherwise buffer until dialog expires */
|
|
39
|
+
enqueuePhase(events: GameEvent[]): void {
|
|
40
|
+
if (this.dialogProtected) {
|
|
41
|
+
this.pendingPhase.push({ events })
|
|
42
|
+
} else {
|
|
43
|
+
this.emitFn(events)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Cancel any active dialog protection and drain all pending phase events */
|
|
48
|
+
flush(): void {
|
|
49
|
+
if (this.dialogTimer) { clearTimeout(this.dialogTimer); this.dialogTimer = null }
|
|
50
|
+
this.dialogProtected = false
|
|
51
|
+
this.dialogTextLength = 0
|
|
52
|
+
this.drainPending()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private drainPending(): void {
|
|
56
|
+
const pending = this.pendingPhase.splice(0)
|
|
57
|
+
for (const p of pending) {
|
|
58
|
+
this.emitFn(p.events)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @desc Exponential backoff reconnection manager for WebSocket connections
|
|
2
|
+
export type ReconnectCallback = () => void
|
|
3
|
+
|
|
4
|
+
/** Schedules reconnection attempts with exponential backoff, resettable on success */
|
|
5
|
+
export class ReconnectManager {
|
|
6
|
+
private attempt = 0
|
|
7
|
+
private maxDelay = 30_000
|
|
8
|
+
private timer: ReturnType<typeof setTimeout> | null = null
|
|
9
|
+
private onReconnect: ReconnectCallback
|
|
10
|
+
|
|
11
|
+
constructor(onReconnect: ReconnectCallback) {
|
|
12
|
+
this.onReconnect = onReconnect
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Schedule the next reconnection attempt with exponential backoff */
|
|
16
|
+
scheduleReconnect(): void {
|
|
17
|
+
const delay = Math.min(1000 * Math.pow(2, this.attempt), this.maxDelay)
|
|
18
|
+
this.attempt++
|
|
19
|
+
console.log(`[ReconnectManager] Reconnecting in ${delay}ms (attempt ${this.attempt})`)
|
|
20
|
+
this.timer = setTimeout(() => {
|
|
21
|
+
this.onReconnect()
|
|
22
|
+
}, delay)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Reset attempt counter and cancel any pending reconnect timer */
|
|
26
|
+
onSuccess(): void {
|
|
27
|
+
this.attempt = 0
|
|
28
|
+
if (this.timer) {
|
|
29
|
+
clearTimeout(this.timer)
|
|
30
|
+
this.timer = null
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Stop all reconnection attempts and reset state */
|
|
35
|
+
stop(): void {
|
|
36
|
+
if (this.timer) {
|
|
37
|
+
clearTimeout(this.timer)
|
|
38
|
+
this.timer = null
|
|
39
|
+
}
|
|
40
|
+
this.attempt = 0
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// @desc A* pathfinding, NPC movement requests with ack/timeout, and destination claim management
|
|
2
|
+
import type { GameEvent } from '../../town-frontend/src/data/GameProtocol.js'
|
|
3
|
+
import { CITIZEN_DESTINATION_POINTS, ROUTE_GRAPHS, type RouteNode, type RouteSceneId } from './data/route-config.js'
|
|
4
|
+
|
|
5
|
+
export const CITIZEN_SPAWN_ORIGIN = { x: 20, z: 24 }
|
|
6
|
+
export const PLAZA_CENTER = { x: 18, z: 13 }
|
|
7
|
+
export const LEAVE_PLAZA_MIN_DISTANCE = 6.2
|
|
8
|
+
export const GREET_RING_RADIUS = 2.2
|
|
9
|
+
export const STEWARD_FACE_POS = { x: 17, z: 15.8 }
|
|
10
|
+
|
|
11
|
+
/** Manages NPC navigation: A* route planning on the town/office graph, move-and-wait with timeout, and destination slot allocation to prevent overcrowding */
|
|
12
|
+
export class RouteManager {
|
|
13
|
+
moveRequestSeq = 0
|
|
14
|
+
pendingMoveRequests = new Map<
|
|
15
|
+
string,
|
|
16
|
+
{
|
|
17
|
+
npcId: string
|
|
18
|
+
resolve: (status: 'arrived' | 'interrupted') => void
|
|
19
|
+
timer: ReturnType<typeof setTimeout>
|
|
20
|
+
}
|
|
21
|
+
>()
|
|
22
|
+
destinationClaimCount = new Map<string, number>()
|
|
23
|
+
npcDestinationClaim = new Map<string, string>()
|
|
24
|
+
npcLastDestination = new Map<string, string>()
|
|
25
|
+
|
|
26
|
+
private emitFn: (events: GameEvent[]) => void
|
|
27
|
+
|
|
28
|
+
constructor(emitFn: (events: GameEvent[]) => void) {
|
|
29
|
+
this.emitFn = emitFn
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
nextMoveRequestId(npcId: string): string {
|
|
33
|
+
this.moveRequestSeq += 1
|
|
34
|
+
return `${npcId}-move-${this.moveRequestSeq}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Called by DirectorBridge when frontend reports NPC arrival or interruption */
|
|
38
|
+
resolveMoveRequest(requestId: string, npcId: string, status: 'arrived' | 'interrupted'): void {
|
|
39
|
+
if (!requestId) return
|
|
40
|
+
const pending = this.pendingMoveRequests.get(requestId)
|
|
41
|
+
if (!pending) return
|
|
42
|
+
clearTimeout(pending.timer)
|
|
43
|
+
this.pendingMoveRequests.delete(requestId)
|
|
44
|
+
if (pending.npcId !== npcId) {
|
|
45
|
+
console.warn(`[RouteManager][MoveAck] requestId=${requestId} npc mismatch expected=${pending.npcId} actual=${npcId}`)
|
|
46
|
+
}
|
|
47
|
+
pending.resolve(status)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Emit a move command and return a promise that resolves when the frontend acks arrival or timeout expires */
|
|
51
|
+
moveNpcAndWait(
|
|
52
|
+
npcId: string,
|
|
53
|
+
target: { x: number; y: number; z: number },
|
|
54
|
+
speed: number,
|
|
55
|
+
timeoutMs = 25000,
|
|
56
|
+
): Promise<'arrived' | 'interrupted'> {
|
|
57
|
+
const requestId = this.nextMoveRequestId(npcId)
|
|
58
|
+
return new Promise<'arrived' | 'interrupted'>((resolve) => {
|
|
59
|
+
const timer = setTimeout(() => {
|
|
60
|
+
this.pendingMoveRequests.delete(requestId)
|
|
61
|
+
console.warn(`[RouteManager][MoveAck] timeout npc=${npcId} requestId=${requestId}`)
|
|
62
|
+
resolve('interrupted')
|
|
63
|
+
}, timeoutMs)
|
|
64
|
+
|
|
65
|
+
this.pendingMoveRequests.set(requestId, { npcId, resolve, timer })
|
|
66
|
+
this.emitFn([{ type: 'npc_move_to', npcId, target, speed, requestId }])
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
distance2D(a: { x: number; z: number }, b: { x: number; z: number }): number {
|
|
71
|
+
const dx = a.x - b.x
|
|
72
|
+
const dz = a.z - b.z
|
|
73
|
+
return Math.sqrt(dx * dx + dz * dz)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Calculate a position on the greeting ring around the steward for the Nth citizen */
|
|
77
|
+
getGreetPoint(idx: number): { x: number; z: number } {
|
|
78
|
+
const angle = (idx * 1.37) % (Math.PI * 2)
|
|
79
|
+
const r = GREET_RING_RADIUS + (Math.random() - 0.5) * 0.45
|
|
80
|
+
return {
|
|
81
|
+
x: STEWARD_FACE_POS.x + Math.cos(angle) * r,
|
|
82
|
+
z: STEWARD_FACE_POS.z + Math.sin(angle) * r,
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
computeLeavePlazaPoint(from: { x: number; z: number }): { x: number; z: number } {
|
|
87
|
+
let dx = from.x - PLAZA_CENTER.x
|
|
88
|
+
let dz = from.z - PLAZA_CENTER.z
|
|
89
|
+
const len = Math.sqrt(dx * dx + dz * dz)
|
|
90
|
+
if (len < 0.0001) {
|
|
91
|
+
const angle = Math.random() * Math.PI * 2
|
|
92
|
+
dx = Math.cos(angle)
|
|
93
|
+
dz = Math.sin(angle)
|
|
94
|
+
} else {
|
|
95
|
+
dx /= len
|
|
96
|
+
dz /= len
|
|
97
|
+
}
|
|
98
|
+
const targetDist = LEAVE_PLAZA_MIN_DISTANCE + 0.7 + Math.random() * 1.8
|
|
99
|
+
return {
|
|
100
|
+
x: PLAZA_CENTER.x + dx * targetDist,
|
|
101
|
+
z: PLAZA_CENTER.z + dz * targetDist,
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Register that an NPC has claimed a destination slot */
|
|
106
|
+
claimDestinationForNpc(npcId: string, destinationId: string): void {
|
|
107
|
+
const prev = this.npcDestinationClaim.get(npcId)
|
|
108
|
+
if (prev === destinationId) return
|
|
109
|
+
if (prev) {
|
|
110
|
+
const prevCount = this.destinationClaimCount.get(prev) ?? 0
|
|
111
|
+
if (prevCount <= 1) this.destinationClaimCount.delete(prev)
|
|
112
|
+
else this.destinationClaimCount.set(prev, prevCount - 1)
|
|
113
|
+
}
|
|
114
|
+
this.npcDestinationClaim.set(npcId, destinationId)
|
|
115
|
+
this.destinationClaimCount.set(destinationId, (this.destinationClaimCount.get(destinationId) ?? 0) + 1)
|
|
116
|
+
this.npcLastDestination.set(npcId, destinationId)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Release a previously claimed destination slot */
|
|
120
|
+
releaseDestinationClaim(npcId: string, destinationId: string): void {
|
|
121
|
+
const current = this.npcDestinationClaim.get(npcId)
|
|
122
|
+
if (current !== destinationId) return
|
|
123
|
+
this.npcDestinationClaim.delete(npcId)
|
|
124
|
+
const count = this.destinationClaimCount.get(destinationId) ?? 0
|
|
125
|
+
if (count <= 1) this.destinationClaimCount.delete(destinationId)
|
|
126
|
+
else this.destinationClaimCount.set(destinationId, count - 1)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
releaseDestinationClaimLater(npcId: string, destinationId: string, delayMs: number): void {
|
|
130
|
+
setTimeout(() => {
|
|
131
|
+
this.releaseDestinationClaim(npcId, destinationId)
|
|
132
|
+
}, delayMs)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Score and select the best destination point for a citizen, avoiding recently visited or overcrowded spots */
|
|
136
|
+
chooseCitizenDestination(npcId: string, from: { x: number; z: number }): { id: string; x: number; z: number; score: number } {
|
|
137
|
+
const last = this.npcLastDestination.get(npcId)
|
|
138
|
+
let best: { id: string; x: number; z: number; score: number } | null = null
|
|
139
|
+
const candidates = CITIZEN_DESTINATION_POINTS.filter((d) => {
|
|
140
|
+
const plazaDistance = this.distance2D({ x: d.x, z: d.z }, PLAZA_CENTER)
|
|
141
|
+
if (plazaDistance < LEAVE_PLAZA_MIN_DISTANCE) return false
|
|
142
|
+
return (this.destinationClaimCount.get(d.id) ?? 0) === 0
|
|
143
|
+
})
|
|
144
|
+
const pool = candidates.length > 0 ? candidates : CITIZEN_DESTINATION_POINTS
|
|
145
|
+
|
|
146
|
+
for (const d of pool) {
|
|
147
|
+
const plazaDistance = this.distance2D({ x: d.x, z: d.z }, PLAZA_CENTER)
|
|
148
|
+
if (plazaDistance < LEAVE_PLAZA_MIN_DISTANCE) continue
|
|
149
|
+
const distFromCurrent = this.distance2D(from, { x: d.x, z: d.z })
|
|
150
|
+
const claimCount = this.destinationClaimCount.get(d.id) ?? 0
|
|
151
|
+
const sameAsLastPenalty = d.id === last ? 8 : 0
|
|
152
|
+
const claimPenalty = claimCount * 6
|
|
153
|
+
const randomBonus = Math.random() * 2.5
|
|
154
|
+
const score = randomBonus - distFromCurrent * 0.42 - claimPenalty - sameAsLastPenalty
|
|
155
|
+
if (!best || score > best.score) {
|
|
156
|
+
best = { id: d.id, x: d.x, z: d.z, score }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (best) return best
|
|
161
|
+
const fallback = CITIZEN_DESTINATION_POINTS[Math.floor(Math.random() * CITIZEN_DESTINATION_POINTS.length)]
|
|
162
|
+
return { id: fallback.id, x: fallback.x, z: fallback.z, score: -999 }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
getRouteGraph(scene: RouteSceneId): Record<string, RouteNode> {
|
|
166
|
+
return ROUTE_GRAPHS[scene] ?? ROUTE_GRAPHS.town
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
inferRouteScene(from: { x: number; z: number }, to: { x: number; z: number }): RouteSceneId {
|
|
170
|
+
if (from.z <= 8.5 && to.z <= 8.5) return 'office'
|
|
171
|
+
return 'town'
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
isRouteDebugEnabled(): boolean {
|
|
175
|
+
try {
|
|
176
|
+
const g = globalThis as unknown as { location?: { search?: string }; localStorage?: { getItem?: (key: string) => string | null } }
|
|
177
|
+
const search = typeof g.location?.search === 'string'
|
|
178
|
+
? new URLSearchParams(g.location.search)
|
|
179
|
+
: null
|
|
180
|
+
if (search?.get('routeDebug') === '1') return true
|
|
181
|
+
const local = g.localStorage?.getItem?.('agentshire_route_debug')
|
|
182
|
+
return local === '1' || local === 'true'
|
|
183
|
+
} catch {
|
|
184
|
+
return false
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
getNearestRouteNodeId(pos: { x: number; z: number }, scene: RouteSceneId): string {
|
|
189
|
+
const graph = this.getRouteGraph(scene)
|
|
190
|
+
const fallbackId = Object.keys(graph)[0] ?? 'plaza_c'
|
|
191
|
+
let bestId = fallbackId
|
|
192
|
+
let bestDist = Number.POSITIVE_INFINITY
|
|
193
|
+
for (const [id, node] of Object.entries(graph)) {
|
|
194
|
+
const d = this.distance2D(pos, node)
|
|
195
|
+
if (d < bestDist) {
|
|
196
|
+
bestDist = d
|
|
197
|
+
bestId = id
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return bestId
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
planRouteNodePath(startId: string, endId: string, scene: RouteSceneId): string[] {
|
|
204
|
+
const graph = this.getRouteGraph(scene)
|
|
205
|
+
if (startId === endId) return [startId]
|
|
206
|
+
const open = new Set<string>([startId])
|
|
207
|
+
const cameFrom = new Map<string, string>()
|
|
208
|
+
const gScore = new Map<string, number>()
|
|
209
|
+
const fScore = new Map<string, number>()
|
|
210
|
+
|
|
211
|
+
const heuristic = (a: string, b: string): number =>
|
|
212
|
+
this.distance2D(graph[a], graph[b])
|
|
213
|
+
|
|
214
|
+
gScore.set(startId, 0)
|
|
215
|
+
fScore.set(startId, heuristic(startId, endId))
|
|
216
|
+
|
|
217
|
+
while (open.size > 0) {
|
|
218
|
+
let current: string | null = null
|
|
219
|
+
let bestF = Number.POSITIVE_INFINITY
|
|
220
|
+
for (const id of open) {
|
|
221
|
+
const f = fScore.get(id) ?? Number.POSITIVE_INFINITY
|
|
222
|
+
if (f < bestF) {
|
|
223
|
+
bestF = f
|
|
224
|
+
current = id
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (!current) break
|
|
228
|
+
if (current === endId) {
|
|
229
|
+
const path: string[] = [current]
|
|
230
|
+
while (cameFrom.has(current)) {
|
|
231
|
+
current = cameFrom.get(current)!
|
|
232
|
+
path.push(current)
|
|
233
|
+
}
|
|
234
|
+
path.reverse()
|
|
235
|
+
return path
|
|
236
|
+
}
|
|
237
|
+
open.delete(current)
|
|
238
|
+
const node = graph[current]
|
|
239
|
+
for (const neighbor of node.neighbors) {
|
|
240
|
+
const tentative = (gScore.get(current) ?? Number.POSITIVE_INFINITY)
|
|
241
|
+
+ this.distance2D(node, graph[neighbor])
|
|
242
|
+
if (tentative < (gScore.get(neighbor) ?? Number.POSITIVE_INFINITY)) {
|
|
243
|
+
cameFrom.set(neighbor, current)
|
|
244
|
+
gScore.set(neighbor, tentative)
|
|
245
|
+
fScore.set(neighbor, tentative + heuristic(neighbor, endId))
|
|
246
|
+
open.add(neighbor)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return [startId, endId]
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Plan a multi-waypoint route through the graph from source to destination */
|
|
255
|
+
planSceneRoute(scene: RouteSceneId, from: { x: number; z: number }, to: { x: number; z: number }): Array<{ x: number; z: number }> {
|
|
256
|
+
const graph = this.getRouteGraph(scene)
|
|
257
|
+
const start = this.getNearestRouteNodeId(from, scene)
|
|
258
|
+
const end = this.getNearestRouteNodeId(to, scene)
|
|
259
|
+
const nodePath = this.planRouteNodePath(start, end, scene)
|
|
260
|
+
const points: Array<{ x: number; z: number }> = []
|
|
261
|
+
|
|
262
|
+
for (const [idx, id] of nodePath.entries()) {
|
|
263
|
+
if (idx === 0 && this.distance2D(from, graph[id]) < 1.2) continue
|
|
264
|
+
if (idx === nodePath.length - 1 && this.distance2D(to, graph[id]) < 1.2) continue
|
|
265
|
+
points.push({ x: graph[id].x, z: graph[id].z })
|
|
266
|
+
}
|
|
267
|
+
points.push({ x: to.x, z: to.z })
|
|
268
|
+
return points
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// @desc Bidirectional agentId ↔ npcId mapping and workstation allocation
|
|
2
|
+
export interface NPCState {
|
|
3
|
+
npcId: string
|
|
4
|
+
agentId: string
|
|
5
|
+
phase: string
|
|
6
|
+
stationId?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const WORKSTATION_IDS = ['B', 'C', 'F', 'G', 'D', 'E', 'H', 'A', 'I', 'J']
|
|
10
|
+
|
|
11
|
+
/** Maintains bidirectional agentId ↔ npcId mappings and allocates workstation slots for sub-agent NPCs */
|
|
12
|
+
export class StateTracker {
|
|
13
|
+
private agentToNpc = new Map<string, string>()
|
|
14
|
+
private npcToAgent = new Map<string, string>()
|
|
15
|
+
private npcStates = new Map<string, NPCState>()
|
|
16
|
+
private usedStations = new Set<string>()
|
|
17
|
+
|
|
18
|
+
readonly stewardNpcId = 'steward'
|
|
19
|
+
|
|
20
|
+
/** Resolve an agentId to its corresponding npcId */
|
|
21
|
+
resolveNpcId(agentId: string): string | undefined {
|
|
22
|
+
return this.agentToNpc.get(agentId)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
resolveAgentId(npcId: string): string | undefined {
|
|
26
|
+
return this.npcToAgent.get(npcId)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getStationForNpc(npcId: string): string | undefined {
|
|
30
|
+
return this.npcStates.get(npcId)?.stationId
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Register a bidirectional agentId ↔ npcId mapping */
|
|
34
|
+
registerMapping(agentId: string, npcId: string): void {
|
|
35
|
+
this.agentToNpc.set(agentId, npcId)
|
|
36
|
+
this.npcToAgent.set(npcId, agentId)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Remove a mapping and associated NPC state by agentId */
|
|
40
|
+
removeMapping(agentId: string): void {
|
|
41
|
+
const npcId = this.agentToNpc.get(agentId)
|
|
42
|
+
if (npcId) {
|
|
43
|
+
const state = this.npcStates.get(npcId)
|
|
44
|
+
if (state?.stationId) {
|
|
45
|
+
this.usedStations.delete(state.stationId)
|
|
46
|
+
}
|
|
47
|
+
this.npcToAgent.delete(npcId)
|
|
48
|
+
this.npcStates.delete(npcId)
|
|
49
|
+
}
|
|
50
|
+
this.agentToNpc.delete(agentId)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
removeMappingByNpcId(npcId: string): void {
|
|
54
|
+
const agentId = this.npcToAgent.get(npcId)
|
|
55
|
+
if (agentId) {
|
|
56
|
+
this.removeMapping(agentId)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Allocate the next available workstation ID, or null if all are in use */
|
|
61
|
+
allocateStation(): string | null {
|
|
62
|
+
for (const id of WORKSTATION_IDS) {
|
|
63
|
+
if (!this.usedStations.has(id)) {
|
|
64
|
+
this.usedStations.add(id)
|
|
65
|
+
return id
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
releaseStation(stationId: string): void {
|
|
72
|
+
this.usedStations.delete(stationId)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
updatePhase(npcId: string, phase: string): void {
|
|
76
|
+
const state = this.npcStates.get(npcId)
|
|
77
|
+
if (state) state.phase = phase
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setStationForNpc(npcId: string, stationId: string): void {
|
|
81
|
+
let state = this.npcStates.get(npcId)
|
|
82
|
+
if (!state) {
|
|
83
|
+
state = { npcId, agentId: this.npcToAgent.get(npcId) ?? '', phase: 'idle' }
|
|
84
|
+
this.npcStates.set(npcId, state)
|
|
85
|
+
}
|
|
86
|
+
state.stationId = stationId
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getAllNpcStates(): NPCState[] {
|
|
90
|
+
return [...this.npcStates.values()]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Reset all mappings, NPC states, and station allocations */
|
|
94
|
+
clear(): void {
|
|
95
|
+
this.agentToNpc.clear()
|
|
96
|
+
this.npcToAgent.clear()
|
|
97
|
+
this.npcStates.clear()
|
|
98
|
+
this.usedStations.clear()
|
|
99
|
+
}
|
|
100
|
+
}
|