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,94 @@
|
|
|
1
|
+
// @desc Pure functions mapping tool names to VFX events, emoji, and NPC animation phases
|
|
2
|
+
import type { GameEvent } from '../../town-frontend/src/data/GameProtocol.js'
|
|
3
|
+
|
|
4
|
+
const IMAGE_EXTENSIONS = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'])
|
|
5
|
+
const VIDEO_EXTENSIONS = new Set(['mp4', 'webm', 'mov', 'm4v'])
|
|
6
|
+
const AUDIO_EXTENSIONS = new Set(['mp3', 'wav', 'ogg', 'm4a', 'aac', 'flac'])
|
|
7
|
+
|
|
8
|
+
const CARD_TYPES: Record<string, string> = {
|
|
9
|
+
game: 'game', app: 'app', website: 'website',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Extract a file path from tool input args (supports read/write/edit_file and bash commands) */
|
|
13
|
+
export function extractFilePath(toolName: string, input: Record<string, unknown>): string | null {
|
|
14
|
+
const FILE_TOOLS = new Set(['read_file', 'write_file', 'edit_file'])
|
|
15
|
+
if (FILE_TOOLS.has(toolName)) return (input.path ?? input.file) as string | null
|
|
16
|
+
if (toolName === 'bash') {
|
|
17
|
+
const cmd = String(input.command ?? '')
|
|
18
|
+
const m = cmd.match(/(?:cat|head|tail|vi|vim|nano|code)\s+["']?([^\s"']+)/)
|
|
19
|
+
return m ? m[1] : null
|
|
20
|
+
}
|
|
21
|
+
return null
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Check if a file path refers to a persona directory */
|
|
25
|
+
export function isPersonaPath(filePath: string | null): boolean {
|
|
26
|
+
if (!filePath) return false
|
|
27
|
+
return /\bpersonas?\b/i.test(filePath)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Return the emoji icon for a given tool name */
|
|
31
|
+
export function toolEmoji(toolName: string): string {
|
|
32
|
+
if (['write_file', 'edit_file'].includes(toolName)) return '💻'
|
|
33
|
+
if (toolName === 'bash') return '⚡'
|
|
34
|
+
if (['read_file', 'grep', 'glob'].includes(toolName)) return '🔍'
|
|
35
|
+
if (toolName === 'web_search' || toolName === 'web_fetch') return '🌐'
|
|
36
|
+
if (toolName === 'skill') return '⚡'
|
|
37
|
+
return '🔧'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const MANAGEMENT_TOOLS = new Set([
|
|
41
|
+
'sessions_spawn', 'sessions_yield', 'next_step', 'create_plan',
|
|
42
|
+
'project_complete', 'register_project', 'spawn_agent',
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
/** Generate GameEvents for NPC animation, emoji, and VFX based on which tool is being used */
|
|
46
|
+
export function toolToVfxEvents(toolName: string, npcId: string, input?: Record<string, unknown>): { events: GameEvent[], phase: string } {
|
|
47
|
+
const events: GameEvent[] = []
|
|
48
|
+
const filePath = extractFilePath(toolName, input ?? {})
|
|
49
|
+
const fileName = filePath?.split('/').pop() ?? ''
|
|
50
|
+
const isPersona = isPersonaPath(filePath)
|
|
51
|
+
|
|
52
|
+
if (MANAGEMENT_TOOLS.has(toolName)) {
|
|
53
|
+
events.push({ type: 'npc_emoji', npcId, emoji: toolEmoji(toolName) })
|
|
54
|
+
return { events, phase: 'thinking' }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (['read_file', 'grep', 'glob'].includes(toolName)) {
|
|
58
|
+
if (isPersona) {
|
|
59
|
+
return { events, phase: 'documenting' }
|
|
60
|
+
}
|
|
61
|
+
events.push({ type: 'npc_anim', npcId, anim: 'reading' })
|
|
62
|
+
if (fileName) events.push({ type: 'fx', effect: 'fileIcon', params: { npcId, fileName } })
|
|
63
|
+
events.push({ type: 'npc_emoji', npcId, emoji: toolEmoji(toolName) })
|
|
64
|
+
} else if (['write_file', 'edit_file'].includes(toolName)) {
|
|
65
|
+
if (isPersona) {
|
|
66
|
+
return { events, phase: 'documenting' }
|
|
67
|
+
}
|
|
68
|
+
events.push({ type: 'npc_anim', npcId, anim: 'typing' })
|
|
69
|
+
if (fileName) events.push({ type: 'fx', effect: 'fileIcon', params: { npcId, fileName } })
|
|
70
|
+
events.push({ type: 'npc_emoji', npcId, emoji: toolEmoji(toolName) })
|
|
71
|
+
} else if (toolName === 'bash') {
|
|
72
|
+
events.push({ type: 'npc_anim', npcId, anim: 'typing' })
|
|
73
|
+
events.push({ type: 'npc_emoji', npcId, emoji: toolEmoji(toolName) })
|
|
74
|
+
} else if (toolName === 'web_search' || toolName === 'web_fetch') {
|
|
75
|
+
events.push({ type: 'npc_anim', npcId, anim: 'reading' })
|
|
76
|
+
events.push({ type: 'fx', effect: 'searchRadar', params: { npcId } })
|
|
77
|
+
events.push({ type: 'npc_emoji', npcId, emoji: toolEmoji(toolName) })
|
|
78
|
+
} else {
|
|
79
|
+
events.push({ type: 'npc_emoji', npcId, emoji: toolEmoji(toolName) })
|
|
80
|
+
}
|
|
81
|
+
return { events, phase: 'working' }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Infer the deliverable card type (image/video/audio/file) from a file extension */
|
|
85
|
+
export function inferDeliverableCardType(filePath: string): 'image' | 'video' | 'audio' | 'file' {
|
|
86
|
+
const normalized = filePath.toLowerCase()
|
|
87
|
+
const extension = normalized.split('.').pop() ?? ''
|
|
88
|
+
if (IMAGE_EXTENSIONS.has(extension)) return 'image'
|
|
89
|
+
if (VIDEO_EXTENSIONS.has(extension)) return 'video'
|
|
90
|
+
if (AUDIO_EXTENSIONS.has(extension)) return 'audio'
|
|
91
|
+
return 'file'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { CARD_TYPES }
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// @desc Tests for ActivityStream: thinking buffer and activity log
|
|
2
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
3
|
+
import { ActivityStream } from '../ActivityStream.js'
|
|
4
|
+
|
|
5
|
+
describe('ActivityStream', () => {
|
|
6
|
+
let emitFn: ReturnType<typeof vi.fn>
|
|
7
|
+
let stream: ActivityStream
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
vi.useFakeTimers()
|
|
11
|
+
emitFn = vi.fn()
|
|
12
|
+
stream = new ActivityStream(emitFn)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
vi.useRealTimers()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
describe('emitActivity', () => {
|
|
20
|
+
it('emits npc_activity event with correct fields', () => {
|
|
21
|
+
stream.emitActivity('npc1', 'wrench', '执行命令')
|
|
22
|
+
expect(emitFn).toHaveBeenCalledWith([
|
|
23
|
+
expect.objectContaining({
|
|
24
|
+
type: 'npc_activity',
|
|
25
|
+
npcId: 'npc1',
|
|
26
|
+
icon: 'wrench',
|
|
27
|
+
message: '执行命令',
|
|
28
|
+
time: expect.any(String),
|
|
29
|
+
}),
|
|
30
|
+
])
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('emitActivityStatus', () => {
|
|
35
|
+
it('marks last activity entry as success', () => {
|
|
36
|
+
stream.emitActivity('npc1', 'wrench', 'task')
|
|
37
|
+
emitFn.mockClear()
|
|
38
|
+
|
|
39
|
+
stream.emitActivityStatus('npc1', true)
|
|
40
|
+
expect(emitFn).toHaveBeenCalledWith([
|
|
41
|
+
expect.objectContaining({
|
|
42
|
+
type: 'npc_activity_status',
|
|
43
|
+
npcId: 'npc1',
|
|
44
|
+
success: true,
|
|
45
|
+
}),
|
|
46
|
+
])
|
|
47
|
+
|
|
48
|
+
const replay = stream.getActivityReplayEvents()
|
|
49
|
+
const activityEntry = replay.find(
|
|
50
|
+
(e: any) => e.type === 'npc_activity' && e.npcId === 'npc1',
|
|
51
|
+
)
|
|
52
|
+
expect(activityEntry.status).toBe(true)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('marks last activity entry as failure', () => {
|
|
56
|
+
stream.emitActivity('npc1', 'wrench', 'task')
|
|
57
|
+
stream.emitActivityStatus('npc1', false)
|
|
58
|
+
|
|
59
|
+
const replay = stream.getActivityReplayEvents()
|
|
60
|
+
const activityEntry = replay.find(
|
|
61
|
+
(e: any) => e.type === 'npc_activity' && e.npcId === 'npc1',
|
|
62
|
+
)
|
|
63
|
+
expect(activityEntry.status).toBe(false)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('appendThinkingDelta + flushThinking', () => {
|
|
68
|
+
it('buffers text and flushes on interval', () => {
|
|
69
|
+
stream.appendThinkingDelta('npc1', 'hello ')
|
|
70
|
+
stream.appendThinkingDelta('npc1', 'world')
|
|
71
|
+
|
|
72
|
+
// startThinkingStream emits initial activity
|
|
73
|
+
expect(emitFn).toHaveBeenCalledWith([
|
|
74
|
+
expect.objectContaining({ type: 'npc_activity', npcId: 'npc1', icon: 'brain' }),
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
emitFn.mockClear()
|
|
78
|
+
vi.advanceTimersByTime(500)
|
|
79
|
+
|
|
80
|
+
expect(emitFn).toHaveBeenCalledWith([
|
|
81
|
+
expect.objectContaining({
|
|
82
|
+
type: 'npc_activity_stream',
|
|
83
|
+
npcId: 'npc1',
|
|
84
|
+
delta: 'hello world',
|
|
85
|
+
}),
|
|
86
|
+
])
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('flushThinking emits remaining buffer and stream_end', () => {
|
|
90
|
+
stream.appendThinkingDelta('npc1', 'remaining')
|
|
91
|
+
emitFn.mockClear()
|
|
92
|
+
|
|
93
|
+
stream.flushThinking('npc1')
|
|
94
|
+
|
|
95
|
+
expect(emitFn).toHaveBeenCalledWith([
|
|
96
|
+
expect.objectContaining({ type: 'npc_activity_stream', delta: 'remaining' }),
|
|
97
|
+
])
|
|
98
|
+
expect(emitFn).toHaveBeenCalledWith([
|
|
99
|
+
expect.objectContaining({ type: 'npc_activity_stream_end', npcId: 'npc1' }),
|
|
100
|
+
])
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('getActivityReplayEvents', () => {
|
|
105
|
+
it('returns cached events as a copy', () => {
|
|
106
|
+
stream.emitActivity('npc1', 'wrench', 'a')
|
|
107
|
+
stream.emitActivity('npc1', 'wrench', 'b')
|
|
108
|
+
|
|
109
|
+
const replay = stream.getActivityReplayEvents()
|
|
110
|
+
expect(replay).toHaveLength(2)
|
|
111
|
+
expect(replay[0].message).toBe('a')
|
|
112
|
+
expect(replay[1].message).toBe('b')
|
|
113
|
+
|
|
114
|
+
replay.push({ type: 'fake' })
|
|
115
|
+
expect(stream.getActivityReplayEvents()).toHaveLength(2)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('activity log cap', () => {
|
|
120
|
+
it('does not exceed MAX_ACTIVITY_LOG (500)', () => {
|
|
121
|
+
for (let i = 0; i < 520; i++) {
|
|
122
|
+
stream.emitActivity('npc1', 'wrench', `msg-${i}`)
|
|
123
|
+
}
|
|
124
|
+
const replay = stream.getActivityReplayEvents()
|
|
125
|
+
expect(replay.length).toBeLessThanOrEqual(500)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
describe('emitTodoActivity', () => {
|
|
130
|
+
it('emits npc_activity_todo event with todos', () => {
|
|
131
|
+
stream.emitTodoActivity('npc1', {
|
|
132
|
+
todos: [
|
|
133
|
+
{ id: 1, content: 'task one', status: 'pending' },
|
|
134
|
+
{ id: 2, content: 'task two', status: 'completed' },
|
|
135
|
+
],
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
expect(emitFn).toHaveBeenCalledWith([
|
|
139
|
+
expect.objectContaining({
|
|
140
|
+
type: 'npc_activity_todo',
|
|
141
|
+
npcId: 'npc1',
|
|
142
|
+
todos: [
|
|
143
|
+
{ id: 1, content: 'task one', status: 'pending' },
|
|
144
|
+
{ id: 2, content: 'task two', status: 'completed' },
|
|
145
|
+
],
|
|
146
|
+
}),
|
|
147
|
+
])
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('does not emit when todos is missing or not an array', () => {
|
|
151
|
+
stream.emitTodoActivity('npc1', {})
|
|
152
|
+
stream.emitTodoActivity('npc1', { todos: 'invalid' })
|
|
153
|
+
expect(emitFn).not.toHaveBeenCalled()
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
})
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// @desc Tests for CitizenManager: citizen detection, name matching, lifecycle
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
3
|
+
import { CitizenManager, type CitizenManagerDeps } from '../CitizenManager.js'
|
|
4
|
+
|
|
5
|
+
vi.mock('../../town-frontend/src/data/CharacterRoster.js', () => ({
|
|
6
|
+
getCharacterKeyForNpc: (id: string) => `char_${id}`,
|
|
7
|
+
}))
|
|
8
|
+
|
|
9
|
+
function createMockDeps(overrides: Partial<CitizenManagerDeps> = {}): CitizenManagerDeps {
|
|
10
|
+
return {
|
|
11
|
+
emit: vi.fn(),
|
|
12
|
+
routes: {
|
|
13
|
+
getGreetPoint: vi.fn(() => ({ x: 17, z: 15 })),
|
|
14
|
+
moveNpcAndWait: vi.fn(async () => 'arrived' as const),
|
|
15
|
+
chooseCitizenDestination: vi.fn(() => ({ id: 'dest_a', x: 30, z: 20, score: 5 })),
|
|
16
|
+
claimDestinationForNpc: vi.fn(),
|
|
17
|
+
releaseDestinationClaim: vi.fn(),
|
|
18
|
+
releaseDestinationClaimLater: vi.fn(),
|
|
19
|
+
distance2D: vi.fn(() => 10),
|
|
20
|
+
} as any,
|
|
21
|
+
getConfig: vi.fn(() => ({
|
|
22
|
+
citizens: [
|
|
23
|
+
{ id: 'citizen_alice', name: 'Alice' },
|
|
24
|
+
{ id: 'citizen_bob', name: 'Bob' },
|
|
25
|
+
],
|
|
26
|
+
})),
|
|
27
|
+
getCharacterAssignments: vi.fn(() => new Map()),
|
|
28
|
+
isStewardConfirmed: vi.fn(() => true),
|
|
29
|
+
getPersonaName: vi.fn(() => 'Steward'),
|
|
30
|
+
getStewardName: vi.fn(() => 'steward'),
|
|
31
|
+
onPersonaChanged: vi.fn(),
|
|
32
|
+
delayMs: vi.fn(async () => {}),
|
|
33
|
+
scheduleDelayedEmit: vi.fn(),
|
|
34
|
+
getLastToolInput: vi.fn(() => ({})),
|
|
35
|
+
...overrides,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('CitizenManager', () => {
|
|
40
|
+
let deps: CitizenManagerDeps
|
|
41
|
+
let cm: CitizenManager
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
deps = createMockDeps()
|
|
45
|
+
cm = new CitizenManager(deps)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('findCitizenNpcId', () => {
|
|
49
|
+
it('returns exact match by name from config', () => {
|
|
50
|
+
expect(cm.findCitizenNpcId('Alice')).toBe('citizen_alice')
|
|
51
|
+
expect(cm.findCitizenNpcId('Bob')).toBe('citizen_bob')
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('returns null for unknown name', () => {
|
|
55
|
+
expect(cm.findCitizenNpcId('Charlie')).toBeNull()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('matches by spawned IDs when not in config', () => {
|
|
59
|
+
cm.spawnedCitizenIds.add('citizen_charlie')
|
|
60
|
+
expect(cm.findCitizenNpcId('Charlie')).toBe('citizen_charlie')
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('fuzzyMatchCitizen', () => {
|
|
65
|
+
it('matches partial name against config', () => {
|
|
66
|
+
expect(cm.fuzzyMatchCitizen('ali')).toBe('citizen_alice')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('returns null for empty string', () => {
|
|
70
|
+
expect(cm.fuzzyMatchCitizen('')).toBeNull()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('matches against spawned citizen IDs', () => {
|
|
74
|
+
cm.spawnedCitizenIds.add('citizen_diana_prince')
|
|
75
|
+
expect(cm.fuzzyMatchCitizen('diana')).toBe('citizen_diana_prince')
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('looksLikeIdFragment', () => {
|
|
80
|
+
it('returns true for short ASCII strings', () => {
|
|
81
|
+
expect(cm.looksLikeIdFragment('abc123')).toBe(true)
|
|
82
|
+
expect(cm.looksLikeIdFragment('npc-1')).toBe(true)
|
|
83
|
+
expect(cm.looksLikeIdFragment('a')).toBe(true)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('returns false for strings with Chinese characters', () => {
|
|
87
|
+
expect(cm.looksLikeIdFragment('小明')).toBe(false)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('returns false for strings longer than 12 characters', () => {
|
|
91
|
+
expect(cm.looksLikeIdFragment('abcdefghijklm')).toBe(false)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('isStewardName', () => {
|
|
96
|
+
it('matches persona name', () => {
|
|
97
|
+
expect(cm.isStewardName('Steward')).toBe(true)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
it('does not match random name', () => {
|
|
101
|
+
expect(cm.isStewardName('RandomPerson')).toBe(false)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('matches when getStewardName returns a custom name', () => {
|
|
105
|
+
deps = createMockDeps({
|
|
106
|
+
getPersonaName: vi.fn(() => null),
|
|
107
|
+
getStewardName: vi.fn(() => 'CustomBot'),
|
|
108
|
+
})
|
|
109
|
+
cm = new CitizenManager(deps)
|
|
110
|
+
expect(cm.isStewardName('CustomBot')).toBe(true)
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
describe('isPersonaPath', () => {
|
|
115
|
+
it('returns true for persona directory paths', () => {
|
|
116
|
+
expect(cm.isPersonaPath('/workspace/personas/alice.md')).toBe(true)
|
|
117
|
+
expect(cm.isPersonaPath('/workspace/persona/bob.md')).toBe(true)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('returns false for non-persona paths', () => {
|
|
121
|
+
expect(cm.isPersonaPath('/workspace/src/main.ts')).toBe(false)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('returns false for null', () => {
|
|
125
|
+
expect(cm.isPersonaPath(null)).toBe(false)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
describe('detectCitizenCreated', () => {
|
|
130
|
+
it('queues citizen for spawn on persona write_file event', () => {
|
|
131
|
+
cm.detectCitizenCreated({
|
|
132
|
+
name: 'write_file',
|
|
133
|
+
meta: { filePath: '/workspace/personas/Charlie.md' },
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
expect(cm.spawnedCitizenIds.has('citizen_charlie')).toBe(true)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('ignores non-write_file events', () => {
|
|
140
|
+
cm.detectCitizenCreated({ name: 'read_file', meta: {} })
|
|
141
|
+
expect(cm.spawnedCitizenIds.size).toBe(0)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('ignores non-persona paths', () => {
|
|
145
|
+
cm.detectCitizenCreated({
|
|
146
|
+
name: 'write_file',
|
|
147
|
+
meta: { filePath: '/workspace/src/main.ts' },
|
|
148
|
+
})
|
|
149
|
+
expect(cm.spawnedCitizenIds.size).toBe(0)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('skips steward persona file', () => {
|
|
153
|
+
cm.detectCitizenCreated({
|
|
154
|
+
name: 'write_file',
|
|
155
|
+
meta: { filePath: '/workspace/personas/Steward.md' },
|
|
156
|
+
})
|
|
157
|
+
expect(cm.spawnedCitizenIds.has('citizen_steward')).toBe(true)
|
|
158
|
+
expect(cm.citizenSpawnQueue).toHaveLength(0)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('does not duplicate already-spawned citizen', () => {
|
|
162
|
+
cm.detectCitizenCreated({
|
|
163
|
+
name: 'write_file',
|
|
164
|
+
meta: { filePath: '/workspace/personas/Charlie.md' },
|
|
165
|
+
})
|
|
166
|
+
const queueLen = cm.citizenSpawnQueue.length
|
|
167
|
+
cm.detectCitizenCreated({
|
|
168
|
+
name: 'write_file',
|
|
169
|
+
meta: { filePath: '/workspace/personas/Charlie.md' },
|
|
170
|
+
})
|
|
171
|
+
expect(cm.citizenSpawnQueue.length).toBeLessThanOrEqual(queueLen)
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
})
|