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.
Files changed (406) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +21 -0
  3. package/README.md +437 -0
  4. package/README.zh-CN.md +441 -0
  5. package/ROADMAP.md +207 -0
  6. package/THIRD_PARTY_NOTICES.md +49 -0
  7. package/VISION.md +101 -0
  8. package/index.ts +386 -0
  9. package/openclaw.plugin.json +27 -0
  10. package/package.json +100 -0
  11. package/src/bridge/AGENTS.md +136 -0
  12. package/src/bridge/ActivityStream.ts +184 -0
  13. package/src/bridge/CitizenManager.ts +280 -0
  14. package/src/bridge/DirectorBridge.ts +1076 -0
  15. package/src/bridge/EventTranslator.ts +142 -0
  16. package/src/bridge/NpcEventQueue.ts +61 -0
  17. package/src/bridge/ReconnectManager.ts +42 -0
  18. package/src/bridge/RouteManager.ts +270 -0
  19. package/src/bridge/StateTracker.ts +100 -0
  20. package/src/bridge/ToolVfxMapper.ts +94 -0
  21. package/src/bridge/__tests__/ActivityStream.test.ts +156 -0
  22. package/src/bridge/__tests__/CitizenManager.test.ts +174 -0
  23. package/src/bridge/__tests__/EventTranslator.test.ts +292 -0
  24. package/src/bridge/__tests__/NpcEventQueue.test.ts +92 -0
  25. package/src/bridge/__tests__/RouteManager.test.ts +106 -0
  26. package/src/bridge/data/route-config.json +36 -0
  27. package/src/bridge/data/route-config.ts +19 -0
  28. package/src/bridge/implicit-chat.ts +192 -0
  29. package/src/bridge/index.ts +12 -0
  30. package/src/contracts/agent-state.ts +87 -0
  31. package/src/contracts/agui.ts +212 -0
  32. package/src/contracts/chat.ts +67 -0
  33. package/src/contracts/events.ts +170 -0
  34. package/src/contracts/index.ts +71 -0
  35. package/src/contracts/media.ts +77 -0
  36. package/src/contracts/registry.ts +32 -0
  37. package/src/plugin/AGENTS.md +192 -0
  38. package/src/plugin/__tests__/editor-serve-megapack.test.ts +150 -0
  39. package/src/plugin/__tests__/hook-translator.test.ts +197 -0
  40. package/src/plugin/auto-config.ts +131 -0
  41. package/src/plugin/channel.ts +399 -0
  42. package/src/plugin/chat-asset-resolver.ts +75 -0
  43. package/src/plugin/chat-session-watcher.ts +98 -0
  44. package/src/plugin/citizen-agent-manager.ts +178 -0
  45. package/src/plugin/citizen-chat-router.ts +83 -0
  46. package/src/plugin/citizen-workshop-manager.ts +96 -0
  47. package/src/plugin/custom-asset-manager.ts +250 -0
  48. package/src/plugin/editor-serve.ts +1368 -0
  49. package/src/plugin/group-discussion.ts +223 -0
  50. package/src/plugin/hook-translator.ts +202 -0
  51. package/src/plugin/llm-agent-proxy.ts +193 -0
  52. package/src/plugin/outbound-adapter.ts +187 -0
  53. package/src/plugin/paths.ts +33 -0
  54. package/src/plugin/plan-manager.ts +594 -0
  55. package/src/plugin/runtime.ts +9 -0
  56. package/src/plugin/session-history.ts +838 -0
  57. package/src/plugin/session-log-watcher.ts +221 -0
  58. package/src/plugin/soul-prompt-template.ts +290 -0
  59. package/src/plugin/subagent-tracker.ts +205 -0
  60. package/src/plugin/tools.ts +493 -0
  61. package/src/plugin/town-session.ts +40 -0
  62. package/src/plugin/ws-server.ts +680 -0
  63. package/src/town-souls.ts +134 -0
  64. package/town-frontend/dist/assets/CustomAssetStore-oi8aIurt.js +2 -0
  65. package/town-frontend/dist/assets/GLTFLoader-BA5RqSME.js +3804 -0
  66. package/town-frontend/dist/assets/OrbitControls-ZmySp9sQ.js +2 -0
  67. package/town-frontend/dist/assets/SettingsPanel-BO52reJe.js +2 -0
  68. package/town-frontend/dist/assets/SkeletonUtils-BCVmgslc.js +2 -0
  69. package/town-frontend/dist/assets/SkillLearnCard-Dk38iDpy.js +6 -0
  70. package/town-frontend/dist/assets/TopicSetupPanel-DLaLHB_Z.js +2 -0
  71. package/town-frontend/dist/assets/Trap-Bold-CT0JBE39.woff2 +0 -0
  72. package/town-frontend/dist/assets/Trap-SemiBold-R4_-Ld0j.woff2 +0 -0
  73. package/town-frontend/dist/assets/WeatherSystem-Cb3BvHes.js +550 -0
  74. package/town-frontend/dist/assets/avatars/char-female-a.webp +0 -0
  75. package/town-frontend/dist/assets/avatars/char-female-b.webp +0 -0
  76. package/town-frontend/dist/assets/avatars/char-female-c.webp +0 -0
  77. package/town-frontend/dist/assets/avatars/char-female-d.webp +0 -0
  78. package/town-frontend/dist/assets/avatars/char-female-e.webp +0 -0
  79. package/town-frontend/dist/assets/avatars/char-female-f.webp +0 -0
  80. package/town-frontend/dist/assets/avatars/char-male-a.webp +0 -0
  81. package/town-frontend/dist/assets/avatars/char-male-b.webp +0 -0
  82. package/town-frontend/dist/assets/avatars/char-male-c.webp +0 -0
  83. package/town-frontend/dist/assets/avatars/char-male-d.webp +0 -0
  84. package/town-frontend/dist/assets/avatars/char-male-e.webp +0 -0
  85. package/town-frontend/dist/assets/avatars/char-male-f.webp +0 -0
  86. package/town-frontend/dist/assets/avatars/char-pet-beaver.webp +0 -0
  87. package/town-frontend/dist/assets/avatars/char-pet-bee.webp +0 -0
  88. package/town-frontend/dist/assets/avatars/char-pet-bunny.webp +0 -0
  89. package/town-frontend/dist/assets/avatars/char-pet-cat.webp +0 -0
  90. package/town-frontend/dist/assets/avatars/char-pet-caterpillar.webp +0 -0
  91. package/town-frontend/dist/assets/avatars/char-pet-chick.webp +0 -0
  92. package/town-frontend/dist/assets/avatars/char-pet-cow.webp +0 -0
  93. package/town-frontend/dist/assets/avatars/char-pet-crab.webp +0 -0
  94. package/town-frontend/dist/assets/avatars/char-pet-deer.webp +0 -0
  95. package/town-frontend/dist/assets/avatars/char-pet-dog.webp +0 -0
  96. package/town-frontend/dist/assets/avatars/char-pet-elephant.webp +0 -0
  97. package/town-frontend/dist/assets/avatars/char-pet-fish.webp +0 -0
  98. package/town-frontend/dist/assets/avatars/char-pet-fox.webp +0 -0
  99. package/town-frontend/dist/assets/avatars/char-pet-giraffe.webp +0 -0
  100. package/town-frontend/dist/assets/avatars/char-pet-hog.webp +0 -0
  101. package/town-frontend/dist/assets/avatars/char-pet-koala.webp +0 -0
  102. package/town-frontend/dist/assets/avatars/char-pet-lion.webp +0 -0
  103. package/town-frontend/dist/assets/avatars/char-pet-monkey.webp +0 -0
  104. package/town-frontend/dist/assets/avatars/char-pet-panda.webp +0 -0
  105. package/town-frontend/dist/assets/avatars/char-pet-parrot.webp +0 -0
  106. package/town-frontend/dist/assets/avatars/char-pet-penguin.webp +0 -0
  107. package/town-frontend/dist/assets/avatars/char-pet-pig.webp +0 -0
  108. package/town-frontend/dist/assets/avatars/char-pet-polar.webp +0 -0
  109. package/town-frontend/dist/assets/avatars/char-pet-tiger.webp +0 -0
  110. package/town-frontend/dist/assets/character-catalog-DSmLtlNC.js +2 -0
  111. package/town-frontend/dist/assets/citizenEditor-DubGSJOQ.js +296 -0
  112. package/town-frontend/dist/assets/command-parser-BUd15Bmv.js +12 -0
  113. package/town-frontend/dist/assets/editor-B5QO0OtX.js +258 -0
  114. package/town-frontend/dist/assets/editor-Bk8g1NCD.css +1 -0
  115. package/town-frontend/dist/assets/index-BWfrufil.js +2 -0
  116. package/town-frontend/dist/assets/index-faS20RJk.js +6 -0
  117. package/town-frontend/dist/assets/logo-DJI6EtST.png +0 -0
  118. package/town-frontend/dist/assets/logo-title-AdKPZX5E.png +0 -0
  119. package/town-frontend/dist/assets/main-CqsN43aT.js +224 -0
  120. package/town-frontend/dist/assets/main-D7neuy3w.css +1 -0
  121. package/town-frontend/dist/assets/models/buildings/base.bin +0 -0
  122. package/town-frontend/dist/assets/models/buildings/base.gltf +136 -0
  123. package/town-frontend/dist/assets/models/buildings/bench.bin +0 -0
  124. package/town-frontend/dist/assets/models/buildings/bench.gltf +136 -0
  125. package/town-frontend/dist/assets/models/buildings/box_A.bin +0 -0
  126. package/town-frontend/dist/assets/models/buildings/box_A.gltf +136 -0
  127. package/town-frontend/dist/assets/models/buildings/box_B.bin +0 -0
  128. package/town-frontend/dist/assets/models/buildings/box_B.gltf +136 -0
  129. package/town-frontend/dist/assets/models/buildings/building_A.bin +0 -0
  130. package/town-frontend/dist/assets/models/buildings/building_A.gltf +136 -0
  131. package/town-frontend/dist/assets/models/buildings/building_A_withoutBase.bin +0 -0
  132. package/town-frontend/dist/assets/models/buildings/building_A_withoutBase.gltf +136 -0
  133. package/town-frontend/dist/assets/models/buildings/building_B.bin +0 -0
  134. package/town-frontend/dist/assets/models/buildings/building_B.gltf +136 -0
  135. package/town-frontend/dist/assets/models/buildings/building_B_withoutBase.bin +0 -0
  136. package/town-frontend/dist/assets/models/buildings/building_B_withoutBase.gltf +136 -0
  137. package/town-frontend/dist/assets/models/buildings/building_C.bin +0 -0
  138. package/town-frontend/dist/assets/models/buildings/building_C.gltf +136 -0
  139. package/town-frontend/dist/assets/models/buildings/building_C_withoutBase.bin +0 -0
  140. package/town-frontend/dist/assets/models/buildings/building_C_withoutBase.gltf +136 -0
  141. package/town-frontend/dist/assets/models/buildings/building_D.bin +0 -0
  142. package/town-frontend/dist/assets/models/buildings/building_D.gltf +136 -0
  143. package/town-frontend/dist/assets/models/buildings/building_D_withoutBase.bin +0 -0
  144. package/town-frontend/dist/assets/models/buildings/building_D_withoutBase.gltf +136 -0
  145. package/town-frontend/dist/assets/models/buildings/building_E.bin +0 -0
  146. package/town-frontend/dist/assets/models/buildings/building_E.gltf +136 -0
  147. package/town-frontend/dist/assets/models/buildings/building_E_withoutBase.bin +0 -0
  148. package/town-frontend/dist/assets/models/buildings/building_E_withoutBase.gltf +136 -0
  149. package/town-frontend/dist/assets/models/buildings/building_F.bin +0 -0
  150. package/town-frontend/dist/assets/models/buildings/building_F.gltf +136 -0
  151. package/town-frontend/dist/assets/models/buildings/building_F_withoutBase.bin +0 -0
  152. package/town-frontend/dist/assets/models/buildings/building_F_withoutBase.gltf +136 -0
  153. package/town-frontend/dist/assets/models/buildings/building_G.bin +0 -0
  154. package/town-frontend/dist/assets/models/buildings/building_G.gltf +136 -0
  155. package/town-frontend/dist/assets/models/buildings/building_G_withoutBase.bin +0 -0
  156. package/town-frontend/dist/assets/models/buildings/building_G_withoutBase.gltf +136 -0
  157. package/town-frontend/dist/assets/models/buildings/building_H.bin +0 -0
  158. package/town-frontend/dist/assets/models/buildings/building_H.gltf +136 -0
  159. package/town-frontend/dist/assets/models/buildings/building_H_withoutBase.bin +0 -0
  160. package/town-frontend/dist/assets/models/buildings/building_H_withoutBase.gltf +136 -0
  161. package/town-frontend/dist/assets/models/buildings/bush.bin +0 -0
  162. package/town-frontend/dist/assets/models/buildings/bush.gltf +136 -0
  163. package/town-frontend/dist/assets/models/buildings/car_hatchback.bin +0 -0
  164. package/town-frontend/dist/assets/models/buildings/car_hatchback.gltf +442 -0
  165. package/town-frontend/dist/assets/models/buildings/car_police.bin +0 -0
  166. package/town-frontend/dist/assets/models/buildings/car_police.gltf +442 -0
  167. package/town-frontend/dist/assets/models/buildings/car_sedan.bin +0 -0
  168. package/town-frontend/dist/assets/models/buildings/car_sedan.gltf +442 -0
  169. package/town-frontend/dist/assets/models/buildings/car_stationwagon.bin +0 -0
  170. package/town-frontend/dist/assets/models/buildings/car_stationwagon.gltf +442 -0
  171. package/town-frontend/dist/assets/models/buildings/car_taxi.bin +0 -0
  172. package/town-frontend/dist/assets/models/buildings/car_taxi.gltf +442 -0
  173. package/town-frontend/dist/assets/models/buildings/citybits_texture.png +0 -0
  174. package/town-frontend/dist/assets/models/buildings/dumpster.bin +0 -0
  175. package/town-frontend/dist/assets/models/buildings/dumpster.gltf +136 -0
  176. package/town-frontend/dist/assets/models/buildings/firehydrant.bin +0 -0
  177. package/town-frontend/dist/assets/models/buildings/firehydrant.gltf +136 -0
  178. package/town-frontend/dist/assets/models/buildings/road_corner.bin +0 -0
  179. package/town-frontend/dist/assets/models/buildings/road_corner.gltf +136 -0
  180. package/town-frontend/dist/assets/models/buildings/road_corner_curved.bin +0 -0
  181. package/town-frontend/dist/assets/models/buildings/road_corner_curved.gltf +136 -0
  182. package/town-frontend/dist/assets/models/buildings/road_junction.bin +0 -0
  183. package/town-frontend/dist/assets/models/buildings/road_junction.gltf +136 -0
  184. package/town-frontend/dist/assets/models/buildings/road_straight.bin +0 -0
  185. package/town-frontend/dist/assets/models/buildings/road_straight.gltf +136 -0
  186. package/town-frontend/dist/assets/models/buildings/road_straight_crossing.bin +0 -0
  187. package/town-frontend/dist/assets/models/buildings/road_straight_crossing.gltf +136 -0
  188. package/town-frontend/dist/assets/models/buildings/road_tsplit.bin +0 -0
  189. package/town-frontend/dist/assets/models/buildings/road_tsplit.gltf +136 -0
  190. package/town-frontend/dist/assets/models/buildings/streetlight.bin +0 -0
  191. package/town-frontend/dist/assets/models/buildings/streetlight.gltf +136 -0
  192. package/town-frontend/dist/assets/models/buildings/trafficlight_A.bin +0 -0
  193. package/town-frontend/dist/assets/models/buildings/trafficlight_A.gltf +136 -0
  194. package/town-frontend/dist/assets/models/buildings/trafficlight_B.bin +0 -0
  195. package/town-frontend/dist/assets/models/buildings/trafficlight_B.gltf +136 -0
  196. package/town-frontend/dist/assets/models/buildings/trafficlight_C.bin +0 -0
  197. package/town-frontend/dist/assets/models/buildings/trafficlight_C.gltf +136 -0
  198. package/town-frontend/dist/assets/models/buildings/trash_A.bin +0 -0
  199. package/town-frontend/dist/assets/models/buildings/trash_A.gltf +136 -0
  200. package/town-frontend/dist/assets/models/buildings/trash_B.bin +0 -0
  201. package/town-frontend/dist/assets/models/buildings/trash_B.gltf +136 -0
  202. package/town-frontend/dist/assets/models/buildings/watertower.bin +0 -0
  203. package/town-frontend/dist/assets/models/buildings/watertower.gltf +136 -0
  204. package/town-frontend/dist/assets/models/characters/Textures/colormap.png +0 -0
  205. package/town-frontend/dist/assets/models/characters/character-female-a.glb +0 -0
  206. package/town-frontend/dist/assets/models/characters/character-female-b.glb +0 -0
  207. package/town-frontend/dist/assets/models/characters/character-female-c.glb +0 -0
  208. package/town-frontend/dist/assets/models/characters/character-female-d.glb +0 -0
  209. package/town-frontend/dist/assets/models/characters/character-female-e.glb +0 -0
  210. package/town-frontend/dist/assets/models/characters/character-female-f.glb +0 -0
  211. package/town-frontend/dist/assets/models/characters/character-male-a.glb +0 -0
  212. package/town-frontend/dist/assets/models/characters/character-male-b.glb +0 -0
  213. package/town-frontend/dist/assets/models/characters/character-male-c.glb +0 -0
  214. package/town-frontend/dist/assets/models/characters/character-male-d.glb +0 -0
  215. package/town-frontend/dist/assets/models/characters/character-male-e.glb +0 -0
  216. package/town-frontend/dist/assets/models/characters/character-male-f.glb +0 -0
  217. package/town-frontend/dist/assets/models/characters/character-pet-beaver.glb +0 -0
  218. package/town-frontend/dist/assets/models/characters/character-pet-bee.glb +0 -0
  219. package/town-frontend/dist/assets/models/characters/character-pet-bunny.glb +0 -0
  220. package/town-frontend/dist/assets/models/characters/character-pet-cat.glb +0 -0
  221. package/town-frontend/dist/assets/models/characters/character-pet-caterpillar.glb +0 -0
  222. package/town-frontend/dist/assets/models/characters/character-pet-chick.glb +0 -0
  223. package/town-frontend/dist/assets/models/characters/character-pet-cow.glb +0 -0
  224. package/town-frontend/dist/assets/models/characters/character-pet-crab.glb +0 -0
  225. package/town-frontend/dist/assets/models/characters/character-pet-deer.glb +0 -0
  226. package/town-frontend/dist/assets/models/characters/character-pet-dog.glb +0 -0
  227. package/town-frontend/dist/assets/models/characters/character-pet-elephant.glb +0 -0
  228. package/town-frontend/dist/assets/models/characters/character-pet-fish.glb +0 -0
  229. package/town-frontend/dist/assets/models/characters/character-pet-fox.glb +0 -0
  230. package/town-frontend/dist/assets/models/characters/character-pet-giraffe.glb +0 -0
  231. package/town-frontend/dist/assets/models/characters/character-pet-hog.glb +0 -0
  232. package/town-frontend/dist/assets/models/characters/character-pet-koala.glb +0 -0
  233. package/town-frontend/dist/assets/models/characters/character-pet-lion.glb +0 -0
  234. package/town-frontend/dist/assets/models/characters/character-pet-monkey.glb +0 -0
  235. package/town-frontend/dist/assets/models/characters/character-pet-panda.glb +0 -0
  236. package/town-frontend/dist/assets/models/characters/character-pet-parrot.glb +0 -0
  237. package/town-frontend/dist/assets/models/characters/character-pet-penguin.glb +0 -0
  238. package/town-frontend/dist/assets/models/characters/character-pet-pig.glb +0 -0
  239. package/town-frontend/dist/assets/models/characters/character-pet-polar.glb +0 -0
  240. package/town-frontend/dist/assets/models/characters/character-pet-tiger.glb +0 -0
  241. package/town-frontend/dist/assets/models/characters/colormap.png +0 -0
  242. package/town-frontend/dist/assets/models/furniture/armchair.bin +0 -0
  243. package/town-frontend/dist/assets/models/furniture/armchair.gltf +136 -0
  244. package/town-frontend/dist/assets/models/furniture/armchair_pillows.bin +0 -0
  245. package/town-frontend/dist/assets/models/furniture/armchair_pillows.gltf +136 -0
  246. package/town-frontend/dist/assets/models/furniture/bed_double_A.bin +0 -0
  247. package/town-frontend/dist/assets/models/furniture/bed_double_A.gltf +136 -0
  248. package/town-frontend/dist/assets/models/furniture/bed_double_B.bin +0 -0
  249. package/town-frontend/dist/assets/models/furniture/bed_double_B.gltf +136 -0
  250. package/town-frontend/dist/assets/models/furniture/bed_single_A.bin +0 -0
  251. package/town-frontend/dist/assets/models/furniture/bed_single_A.gltf +136 -0
  252. package/town-frontend/dist/assets/models/furniture/bed_single_B.bin +0 -0
  253. package/town-frontend/dist/assets/models/furniture/bed_single_B.gltf +136 -0
  254. package/town-frontend/dist/assets/models/furniture/book_set.bin +0 -0
  255. package/town-frontend/dist/assets/models/furniture/book_set.gltf +136 -0
  256. package/town-frontend/dist/assets/models/furniture/book_single.bin +0 -0
  257. package/town-frontend/dist/assets/models/furniture/book_single.gltf +136 -0
  258. package/town-frontend/dist/assets/models/furniture/cabinet_medium.bin +0 -0
  259. package/town-frontend/dist/assets/models/furniture/cabinet_medium.gltf +136 -0
  260. package/town-frontend/dist/assets/models/furniture/cabinet_medium_decorated.bin +0 -0
  261. package/town-frontend/dist/assets/models/furniture/cabinet_medium_decorated.gltf +136 -0
  262. package/town-frontend/dist/assets/models/furniture/cabinet_small.bin +0 -0
  263. package/town-frontend/dist/assets/models/furniture/cabinet_small.gltf +136 -0
  264. package/town-frontend/dist/assets/models/furniture/cabinet_small_decorated.bin +0 -0
  265. package/town-frontend/dist/assets/models/furniture/cabinet_small_decorated.gltf +136 -0
  266. package/town-frontend/dist/assets/models/furniture/cactus_medium_A.bin +0 -0
  267. package/town-frontend/dist/assets/models/furniture/cactus_medium_A.gltf +136 -0
  268. package/town-frontend/dist/assets/models/furniture/cactus_medium_B.bin +0 -0
  269. package/town-frontend/dist/assets/models/furniture/cactus_medium_B.gltf +136 -0
  270. package/town-frontend/dist/assets/models/furniture/cactus_small_A.bin +0 -0
  271. package/town-frontend/dist/assets/models/furniture/cactus_small_A.gltf +136 -0
  272. package/town-frontend/dist/assets/models/furniture/cactus_small_B.bin +0 -0
  273. package/town-frontend/dist/assets/models/furniture/cactus_small_B.gltf +136 -0
  274. package/town-frontend/dist/assets/models/furniture/chair_A.bin +0 -0
  275. package/town-frontend/dist/assets/models/furniture/chair_A.gltf +136 -0
  276. package/town-frontend/dist/assets/models/furniture/chair_A_wood.bin +0 -0
  277. package/town-frontend/dist/assets/models/furniture/chair_A_wood.gltf +136 -0
  278. package/town-frontend/dist/assets/models/furniture/chair_B.bin +0 -0
  279. package/town-frontend/dist/assets/models/furniture/chair_B.gltf +136 -0
  280. package/town-frontend/dist/assets/models/furniture/chair_B_wood.bin +0 -0
  281. package/town-frontend/dist/assets/models/furniture/chair_B_wood.gltf +136 -0
  282. package/town-frontend/dist/assets/models/furniture/chair_C.bin +0 -0
  283. package/town-frontend/dist/assets/models/furniture/chair_C.gltf +136 -0
  284. package/town-frontend/dist/assets/models/furniture/chair_stool.bin +0 -0
  285. package/town-frontend/dist/assets/models/furniture/chair_stool.gltf +136 -0
  286. package/town-frontend/dist/assets/models/furniture/chair_stool_wood.bin +0 -0
  287. package/town-frontend/dist/assets/models/furniture/chair_stool_wood.gltf +136 -0
  288. package/town-frontend/dist/assets/models/furniture/couch.bin +0 -0
  289. package/town-frontend/dist/assets/models/furniture/couch.gltf +136 -0
  290. package/town-frontend/dist/assets/models/furniture/couch_pillows.bin +0 -0
  291. package/town-frontend/dist/assets/models/furniture/couch_pillows.gltf +136 -0
  292. package/town-frontend/dist/assets/models/furniture/furniturebits_texture.png +0 -0
  293. package/town-frontend/dist/assets/models/furniture/lamp_standing.bin +0 -0
  294. package/town-frontend/dist/assets/models/furniture/lamp_standing.gltf +136 -0
  295. package/town-frontend/dist/assets/models/furniture/lamp_table.bin +0 -0
  296. package/town-frontend/dist/assets/models/furniture/lamp_table.gltf +136 -0
  297. package/town-frontend/dist/assets/models/furniture/pictureframe_large_A.bin +0 -0
  298. package/town-frontend/dist/assets/models/furniture/pictureframe_large_A.gltf +136 -0
  299. package/town-frontend/dist/assets/models/furniture/pictureframe_large_B.bin +0 -0
  300. package/town-frontend/dist/assets/models/furniture/pictureframe_large_B.gltf +136 -0
  301. package/town-frontend/dist/assets/models/furniture/pictureframe_medium.bin +0 -0
  302. package/town-frontend/dist/assets/models/furniture/pictureframe_medium.gltf +136 -0
  303. package/town-frontend/dist/assets/models/furniture/pictureframe_small_A.bin +0 -0
  304. package/town-frontend/dist/assets/models/furniture/pictureframe_small_A.gltf +136 -0
  305. package/town-frontend/dist/assets/models/furniture/pictureframe_small_B.bin +0 -0
  306. package/town-frontend/dist/assets/models/furniture/pictureframe_small_B.gltf +136 -0
  307. package/town-frontend/dist/assets/models/furniture/pictureframe_small_C.bin +0 -0
  308. package/town-frontend/dist/assets/models/furniture/pictureframe_small_C.gltf +136 -0
  309. package/town-frontend/dist/assets/models/furniture/pictureframe_standing_A.bin +0 -0
  310. package/town-frontend/dist/assets/models/furniture/pictureframe_standing_A.gltf +136 -0
  311. package/town-frontend/dist/assets/models/furniture/pictureframe_standing_B.bin +0 -0
  312. package/town-frontend/dist/assets/models/furniture/pictureframe_standing_B.gltf +136 -0
  313. package/town-frontend/dist/assets/models/furniture/pillow_A.bin +0 -0
  314. package/town-frontend/dist/assets/models/furniture/pillow_A.gltf +136 -0
  315. package/town-frontend/dist/assets/models/furniture/pillow_B.bin +0 -0
  316. package/town-frontend/dist/assets/models/furniture/pillow_B.gltf +136 -0
  317. package/town-frontend/dist/assets/models/furniture/rug_oval_A.bin +0 -0
  318. package/town-frontend/dist/assets/models/furniture/rug_oval_A.gltf +136 -0
  319. package/town-frontend/dist/assets/models/furniture/rug_oval_B.bin +0 -0
  320. package/town-frontend/dist/assets/models/furniture/rug_oval_B.gltf +136 -0
  321. package/town-frontend/dist/assets/models/furniture/rug_rectangle_A.bin +0 -0
  322. package/town-frontend/dist/assets/models/furniture/rug_rectangle_A.gltf +136 -0
  323. package/town-frontend/dist/assets/models/furniture/rug_rectangle_B.bin +0 -0
  324. package/town-frontend/dist/assets/models/furniture/rug_rectangle_B.gltf +136 -0
  325. package/town-frontend/dist/assets/models/furniture/rug_rectangle_stripes_A.bin +0 -0
  326. package/town-frontend/dist/assets/models/furniture/rug_rectangle_stripes_A.gltf +136 -0
  327. package/town-frontend/dist/assets/models/furniture/rug_rectangle_stripes_B.bin +0 -0
  328. package/town-frontend/dist/assets/models/furniture/rug_rectangle_stripes_B.gltf +136 -0
  329. package/town-frontend/dist/assets/models/furniture/shelf_A_big.bin +0 -0
  330. package/town-frontend/dist/assets/models/furniture/shelf_A_big.gltf +136 -0
  331. package/town-frontend/dist/assets/models/furniture/shelf_A_small.bin +0 -0
  332. package/town-frontend/dist/assets/models/furniture/shelf_A_small.gltf +136 -0
  333. package/town-frontend/dist/assets/models/furniture/shelf_B_large.bin +0 -0
  334. package/town-frontend/dist/assets/models/furniture/shelf_B_large.gltf +136 -0
  335. package/town-frontend/dist/assets/models/furniture/shelf_B_large_decorated.bin +0 -0
  336. package/town-frontend/dist/assets/models/furniture/shelf_B_large_decorated.gltf +136 -0
  337. package/town-frontend/dist/assets/models/furniture/shelf_B_small.bin +0 -0
  338. package/town-frontend/dist/assets/models/furniture/shelf_B_small.gltf +136 -0
  339. package/town-frontend/dist/assets/models/furniture/shelf_B_small_decorated.bin +0 -0
  340. package/town-frontend/dist/assets/models/furniture/shelf_B_small_decorated.gltf +136 -0
  341. package/town-frontend/dist/assets/models/furniture/table_low.bin +0 -0
  342. package/town-frontend/dist/assets/models/furniture/table_low.gltf +136 -0
  343. package/town-frontend/dist/assets/models/furniture/table_medium.bin +0 -0
  344. package/town-frontend/dist/assets/models/furniture/table_medium.gltf +136 -0
  345. package/town-frontend/dist/assets/models/furniture/table_medium_long.bin +0 -0
  346. package/town-frontend/dist/assets/models/furniture/table_medium_long.gltf +136 -0
  347. package/town-frontend/dist/assets/models/furniture/table_small.bin +0 -0
  348. package/town-frontend/dist/assets/models/furniture/table_small.gltf +136 -0
  349. package/town-frontend/dist/assets/models/props/bench.bin +0 -0
  350. package/town-frontend/dist/assets/models/props/bench.gltf +136 -0
  351. package/town-frontend/dist/assets/models/props/bush.bin +0 -0
  352. package/town-frontend/dist/assets/models/props/bush.gltf +136 -0
  353. package/town-frontend/dist/assets/models/props/capybara.glb +0 -0
  354. package/town-frontend/dist/assets/models/props/car_hatchback.bin +0 -0
  355. package/town-frontend/dist/assets/models/props/car_hatchback.gltf +442 -0
  356. package/town-frontend/dist/assets/models/props/car_sedan.bin +0 -0
  357. package/town-frontend/dist/assets/models/props/car_sedan.gltf +442 -0
  358. package/town-frontend/dist/assets/models/props/car_taxi.bin +0 -0
  359. package/town-frontend/dist/assets/models/props/car_taxi.gltf +442 -0
  360. package/town-frontend/dist/assets/models/props/citybits_texture.png +0 -0
  361. package/town-frontend/dist/assets/models/props/dumpster.bin +0 -0
  362. package/town-frontend/dist/assets/models/props/dumpster.gltf +136 -0
  363. package/town-frontend/dist/assets/models/props/firehydrant.bin +0 -0
  364. package/town-frontend/dist/assets/models/props/firehydrant.gltf +136 -0
  365. package/town-frontend/dist/assets/models/props/streetlight.bin +0 -0
  366. package/town-frontend/dist/assets/models/props/streetlight.gltf +136 -0
  367. package/town-frontend/dist/assets/models/props/trafficlight_A.bin +0 -0
  368. package/town-frontend/dist/assets/models/props/trafficlight_A.gltf +136 -0
  369. package/town-frontend/dist/assets/models/props/trash_A.bin +0 -0
  370. package/town-frontend/dist/assets/models/props/trash_A.gltf +136 -0
  371. package/town-frontend/dist/assets/models/props/watertower.bin +0 -0
  372. package/town-frontend/dist/assets/models/props/watertower.gltf +136 -0
  373. package/town-frontend/dist/assets/models/stage-deco/Flowers_1_D.glb +0 -0
  374. package/town-frontend/dist/assets/models/stage-deco/Flowers_2_B.glb +0 -0
  375. package/town-frontend/dist/assets/models/stage-deco/Grass_A_1.glb +0 -0
  376. package/town-frontend/dist/assets/models/stage-deco/Park_GrassHill_A.glb +0 -0
  377. package/town-frontend/dist/assets/models/stage-deco/Pebles_1_A_2.glb +0 -0
  378. package/town-frontend/dist/assets/music/bgm_day.mp3 +0 -0
  379. package/town-frontend/dist/assets/music/bgm_dusk.mp3 +0 -0
  380. package/town-frontend/dist/assets/music/bgm_night.mp3 +0 -0
  381. package/town-frontend/dist/assets/music/bgm_work.mp3 +0 -0
  382. package/town-frontend/dist/assets/office-whiteboard-idle-CyEwBrq_.webp +0 -0
  383. package/town-frontend/dist/assets/preview-B6hYEQij.js +2 -0
  384. package/town-frontend/dist/assets/town-BKesnERP.js +8888 -0
  385. package/town-frontend/dist/assets/town-BnswKsjF.css +1 -0
  386. package/town-frontend/dist/citizen-editor.html +166 -0
  387. package/town-frontend/dist/editor.html +227 -0
  388. package/town-frontend/dist/index.html +22 -0
  389. package/town-frontend/dist/preview.html +56 -0
  390. package/town-frontend/dist/town.html +165 -0
  391. package/town-frontend/dist/viewer.html +91 -0
  392. package/town-souls/CHEN.md +130 -0
  393. package/town-souls/CHENGZI.md +92 -0
  394. package/town-souls/CITIZEN_tpl.md +16 -0
  395. package/town-souls/DIANDIAN.md +92 -0
  396. package/town-souls/HAITANG.md +94 -0
  397. package/town-souls/QIQI.md +119 -0
  398. package/town-souls/SOUL.md +141 -0
  399. package/town-souls/SOUL_tpl.md +135 -0
  400. package/town-souls/XIAOLIE.md +107 -0
  401. package/town-souls/YAN.md +107 -0
  402. package/town-workspace/IDENTITY.md +5 -0
  403. package/town-workspace/SOUL.md +141 -0
  404. package/town-workspace/project-workflow.md +81 -0
  405. package/town-workspace/town-defaults.json +92 -0
  406. package/town-workspace/town-guide.md +45 -0
@@ -0,0 +1,136 @@
1
+ # Bridge 层架构指南
2
+
3
+ > AgentEvent(OpenClaw 协议)→ GameEvent(3D 前端消费)的翻译与编排层。
4
+
5
+ ## 目录结构
6
+
7
+ ```
8
+ src/bridge/
9
+ ├── DirectorBridge.ts # 中央编排器:7阶段Phase状态机 + 事件分发
10
+ ├── EventTranslator.ts # 兜底翻译:AgentEvent → GameEvent 简单映射
11
+ ├── RouteManager.ts # A*寻路 + moveNpcAndWait(带ack/超时) + 目的地评分
12
+ ├── CitizenManager.ts # 居民生命周期:persona检测 → spawn动画编排 → 寻路
13
+ ├── ActivityStream.ts # 活动日志 + thinking流缓冲(500ms flush)
14
+ ├── NpcEventQueue.ts # 对话/phase事件排队(保护气泡不被快速覆盖)
15
+ ├── StateTracker.ts # agentId↔npcId双向映射 + 工位池(A-J共10个)
16
+ ├── ToolVfxMapper.ts # 纯函数:工具名 → VFX事件/emoji/动画phase
17
+ ├── ReconnectManager.ts # WebSocket指数退避重连
18
+ ├── implicit-chat.ts # NPC隐式LLM调用(10种场景,DI注入)
19
+ ├── index.ts # barrel export
20
+ └── data/
21
+ ├── route-config.ts # 路由图类型 + 导入
22
+ └── route-config.json # 小镇/办公室节点图 + 居民目的地坐标
23
+ ```
24
+
25
+ ## 数据流
26
+
27
+ ```
28
+ hook-translator (plugin层) → AgentEvent
29
+
30
+ ▼ WebSocket
31
+ DirectorBridge.processAgentEvent(event)
32
+ ├─ sub_agent.started → 注册映射 + npc_spawn + 启动summon收集窗口(3s)
33
+ ├─ sub_agent.progress → 转发内部事件给对应NPC的queue
34
+ ├─ sub_agent.done → npc_work_done + 更新进度
35
+ ├─ text/text_delta → handleStewardText() → 对话气泡 + activity
36
+ ├─ thinking_delta → ActivityStream.appendThinkingDelta()
37
+ ├─ tool_use → ActivityStream + ToolVfxMapper驱动VFX
38
+ ├─ tool_result → activity状态 + CitizenManager检测(persona/citizen)
39
+ ├─ turn_end → flush所有队列 + flushPendingCitizens
40
+ ├─ bus_message → NPC互看 + connectionBeam VFX
41
+ ├─ hook_activity → hookFlash VFX
42
+ ├─ media_output → deliverable_card
43
+ ├─ error → NPC error phase
44
+ └─ default → EventTranslator.translate() (兜底)
45
+
46
+ ▼ emit GameEvent[]
47
+ MainScene.handleGameEvent()
48
+
49
+ DirectorBridge.processCitizenEvent(npcId, event)
50
+ → 居民Agent(非子Agent)的独立事件流,附加npcId后处理
51
+ ```
52
+
53
+ ## Phase 状态机
54
+
55
+ ```
56
+ idle → summoning → assigning → going_to_office → working → publishing → returning → idle
57
+ ```
58
+
59
+ | Phase | 触发条件 | 发出事件 | 前端编排 |
60
+ |-------|---------|---------|---------|
61
+ | idle | 初始/返回完成 | — | — |
62
+ | summoning | 首个 `sub_agent.started` | `workflow_summon`(收集窗口3s后) | SummonOrchestrator |
63
+ | assigning | 前端 `workflow_phase_complete(summoning)` | `workflow_assign` | BriefingOrchestrator |
64
+ | going_to_office | 前端 `workflow_phase_complete(assigning)` | `workflow_go_office` + 工位分配 | SceneSwitcher + WorkflowHandler |
65
+ | working | 前端 `workflow_phase_complete(going_to_office)` | `progress` + 转发sub_agent事件 | 工位屏幕 + 小游戏 |
66
+ | publishing | `project_complete` tool_result | `workflow_publish` + deliverable_card | CelebrationOrchestrator |
67
+ | returning | 前端 `workflow_phase_complete(publishing)` | `workflow_return` | Choreographer散场 |
68
+
69
+ **迟到者处理**:working 阶段到达的 `sub_agent.started` 不走 summoning,直接分配工位。
70
+
71
+ **临时工机制**:未在 TownConfig 中配置的 agent 自动分配随机 avatar,标记为临时工。
72
+
73
+ ## implicit-chat:NPC 隐式 LLM 调用
74
+
75
+ 通过 `setImplicitChatFn()` 依赖注入实际调用函数(浏览器端走 WebSocket → llm-agent-proxy → OpenClaw embedded agent,Node端可直连)。
76
+
77
+ 10 种场景及 maxTokens:
78
+
79
+ | 场景 | 用途 | maxTokens |
80
+ |------|------|-----------|
81
+ | `daily_plan` | 黎明日计划 | 200 |
82
+ | `encounter_init` | 发起对话 | 120 |
83
+ | `encounter_reply` | 回复对话 | 100 |
84
+ | `encounter_summary` | 对话摘要 | 80 |
85
+ | `tactical_decision` | L2战术决策 | 60 |
86
+ | `night_reflection` | 夜间反思 | 120 |
87
+ | `town_journal` | 每日叙事 | 300 |
88
+ | `greeting` | 问候 | 60 |
89
+ | `farewell` | 告别 | 60 |
90
+ | `micro_reaction` | 微反应 | 60 |
91
+
92
+ 回退机制:无 chatFn / 超时(8s) / 异常时返回预设中文回退文本。
93
+
94
+ ## 模块职责与依赖
95
+
96
+ | 模块 | 职责 | 依赖 |
97
+ |------|------|------|
98
+ | DirectorBridge | Phase状态机 + 事件分发 + 居民事件管道 | StateTracker, EventTranslator, NpcEventQueue, RouteManager, ActivityStream, CitizenManager, ToolVfxMapper |
99
+ | EventTranslator | AgentEvent→GameEvent 简单映射(兜底) | StateTracker |
100
+ | RouteManager | A*寻路 + moveNpcAndWait + 目的地评分 | route-config |
101
+ | CitizenManager | persona检测 + 居民spawn动画序列 | RouteManager, CharacterRoster(DI注入) |
102
+ | ActivityStream | 活动日志 + thinking流 + todo活动 | — |
103
+ | NpcEventQueue | 对话保护期 + phase缓冲 | — |
104
+ | StateTracker | 双向ID映射 + 工位池管理 | — |
105
+ | ToolVfxMapper | 工具→VFX/动画 纯函数 | — |
106
+ | implicit-chat | 隐式LLM调用 + 统计 | DI注入chatFn |
107
+
108
+ ## 编排原则
109
+
110
+ - **Bridge 发高级意图事件,不做 setTimeout 微操**:NPC 完成时发 `npc_work_done`,前端 `WorkflowHandler.handleNpcWorkDone()` 用 async/await 编排完整序列
111
+ - **工位延迟释放**:Bridge 在 `sub_agent.done` 时不立即释放,等前端 `workstation_released` action 回传后才释放(12s安全网兜底)
112
+ - **Phase 前进靠前端回传**:前端通过 `workflow_phase_complete` 推动状态机,Bridge 不假设动画时长
113
+
114
+ ## 常见改动
115
+
116
+ | 要做的事 | 改哪里 |
117
+ |---------|--------|
118
+ | 新增 AgentEvent → GameEvent 映射 | `EventTranslator.translate()` 加 case |
119
+ | 修改工具VFX/动画 | `ToolVfxMapper.ts` |
120
+ | 修改活动日志描述/图标 | `ActivityStream.ts` 的 `toolActivityMsg/toolActivityIcon` |
121
+ | 修改居民spawn动画序列 | `CitizenManager.spawnCitizenSequence()` |
122
+ | 修改寻路图节点 | `data/route-config.json` |
123
+ | 修改Phase编排 | `DirectorBridge` 的 phase 切换逻辑(发意图事件,不编排动画) |
124
+ | 新增隐式LLM场景 | `implicit-chat.ts` 的 `SCENE_CONFIG` |
125
+ | 修改对话保护时长 | `NpcEventQueue.ts` 的 `calcDialogDuration()` |
126
+
127
+ ## 测试
128
+
129
+ ```
130
+ src/bridge/__tests__/
131
+ ├── EventTranslator.test.ts # AgentEvent → GameEvent 映射
132
+ ├── NpcEventQueue.test.ts # 对话保护 + phase 缓冲
133
+ ├── RouteManager.test.ts # A*寻路 + 移动 + 目的地
134
+ ├── ActivityStream.test.ts # thinking流 + 活动日志
135
+ └── CitizenManager.test.ts # 居民检测 + 名字匹配
136
+ ```
@@ -0,0 +1,184 @@
1
+ // @desc NPC activity log and thinking stream — buffers thinking deltas and emits activity/status events
2
+ import type { GameEvent } from '../../town-frontend/src/data/GameProtocol.js'
3
+
4
+ const MAX_ACTIVITY_LOG = 500
5
+
6
+ /** Manages the activity log panel: emits activity entries, streams thinking text in batches, and tracks tool result success/failure */
7
+ export class ActivityStream {
8
+ private thinkingBuffers = new Map<string, string>()
9
+ private thinkingFlushTimers = new Map<string, ReturnType<typeof setTimeout>>()
10
+ private activityLog: any[] = []
11
+ private emitFn: (events: GameEvent[]) => void
12
+
13
+ constructor(emitFn: (events: GameEvent[]) => void) {
14
+ this.emitFn = emitFn
15
+ }
16
+
17
+ private nowHHMM(): string {
18
+ const d = new Date()
19
+ return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
20
+ }
21
+
22
+ private cacheActivity(event: any): void {
23
+ this.activityLog.push(event)
24
+ if (this.activityLog.length > MAX_ACTIVITY_LOG) this.activityLog.shift()
25
+ }
26
+
27
+ getActivityReplayEvents(): any[] {
28
+ return this.activityLog.slice()
29
+ }
30
+
31
+ /** Emit an activity log entry with icon, message, and timestamp */
32
+ emitActivity(npcId: string, icon: string, message: string, noStatus?: boolean): void {
33
+ const ev: GameEvent = { type: 'npc_activity', npcId, icon, message, time: this.nowHHMM() }
34
+ if (noStatus) (ev as any).status = null
35
+ this.cacheActivity(ev)
36
+ this.emitFn([ev])
37
+ }
38
+
39
+ /** Mark the most recent activity entry for an NPC as success or failure */
40
+ emitActivityStatus(npcId: string, success: boolean): void {
41
+ for (let i = 0; i < this.activityLog.length; i++) {
42
+ const cached = this.activityLog[i]
43
+ if (cached.npcId === npcId && cached.type === 'npc_activity' && cached.status === undefined) {
44
+ cached.status = success
45
+ break
46
+ }
47
+ }
48
+ const ev: GameEvent = { type: 'npc_activity_status', npcId, success }
49
+ this.cacheActivity(ev)
50
+ this.emitFn([ev])
51
+ }
52
+
53
+ private startThinkingStream(npcId: string): void {
54
+ if (this.thinkingFlushTimers.has(npcId)) return
55
+ const ev: GameEvent = { type: 'npc_activity', npcId, icon: 'brain', message: '', time: this.nowHHMM() }
56
+ this.cacheActivity(ev)
57
+ this.emitFn([ev])
58
+ const timer = setInterval(() => {
59
+ const buf = this.thinkingBuffers.get(npcId)
60
+ if (buf && buf.length > 0) {
61
+ const streamEv: GameEvent = { type: 'npc_activity_stream', npcId, delta: buf }
62
+ this.cacheActivity(streamEv)
63
+ this.emitFn([streamEv])
64
+ this.thinkingBuffers.set(npcId, '')
65
+ }
66
+ }, 500)
67
+ this.thinkingFlushTimers.set(npcId, timer)
68
+ }
69
+
70
+ /** Buffer a thinking text delta; starts a periodic flush stream if not already running */
71
+ appendThinkingDelta(npcId: string, delta: string): void {
72
+ if (!delta && !this.thinkingFlushTimers.has(npcId)) return
73
+ const buf = this.thinkingBuffers.get(npcId) ?? ''
74
+ this.thinkingBuffers.set(npcId, buf + delta)
75
+ if (!this.thinkingFlushTimers.has(npcId)) {
76
+ this.startThinkingStream(npcId)
77
+ }
78
+ }
79
+
80
+ /** Flush any buffered thinking text and emit stream-end marker */
81
+ flushThinking(npcId: string): void {
82
+ const timer = this.thinkingFlushTimers.get(npcId)
83
+ if (!timer) return
84
+ clearInterval(timer)
85
+ this.thinkingFlushTimers.delete(npcId)
86
+ const buf = this.thinkingBuffers.get(npcId)
87
+ if (buf && buf.length > 0) {
88
+ const streamEv: GameEvent = { type: 'npc_activity_stream', npcId, delta: buf }
89
+ this.cacheActivity(streamEv)
90
+ this.emitFn([streamEv])
91
+ }
92
+ this.thinkingBuffers.set(npcId, '')
93
+ const endEv: GameEvent = { type: 'npc_activity_stream_end', npcId }
94
+ this.cacheActivity(endEv)
95
+ this.emitFn([endEv])
96
+ }
97
+
98
+ /** Return the Lucide icon name for a given tool */
99
+ toolActivityIcon(toolName: string): string {
100
+ if (toolName === '__thinking__') return 'sparkles'
101
+ if (toolName === '__thinking_placeholder__') return 'sparkles'
102
+ if (toolName === 'bash' || toolName === 'exec') return 'terminal'
103
+ if (['read', 'read_file', 'grep', 'glob'].includes(toolName)) return 'file-search'
104
+ if (['write', 'edit', 'write_file', 'edit_file'].includes(toolName)) return 'file-edit'
105
+ if (toolName === 'web_search' || toolName === 'web_fetch') return 'globe'
106
+ if (toolName === 'browser') return 'globe'
107
+ if (toolName === 'process') return 'terminal'
108
+ if (toolName === 'skill') return 'zap'
109
+ if (toolName === 'spawn_agent' || toolName === 'sessions_spawn') return 'users'
110
+ if (toolName === 'todo_write') return 'list-checks'
111
+ return 'wrench'
112
+ }
113
+
114
+ /** Generate a human-readable Chinese description of what a tool is doing */
115
+ toolActivityMsg(toolName: string, input?: Record<string, unknown>): string {
116
+ const inp = input ?? {}
117
+ if (toolName === '__thinking__') {
118
+ const content = String(inp.content ?? '')
119
+ const firstLine = content.split('\n')[0].trim()
120
+ const preview = firstLine.length > 60 ? firstLine.slice(0, 57) + '...' : firstLine
121
+ return `思考完成\n${preview}`
122
+ }
123
+ if (toolName === '__thinking_placeholder__') return '正在思考'
124
+ if (toolName === 'bash' || toolName === 'exec') {
125
+ const cmd = String(inp.command ?? '').trim()
126
+ if (/\b(pnpm|npm|yarn)\s+(install|i|ci)\b/.test(cmd)) return '安装依赖'
127
+ if (/\b(git\s+clone|cp\s+-r|rsync)\b/.test(cmd)) return '克隆项目'
128
+ if (/\bmkdir\b/.test(cmd)) return '创建目录'
129
+ if (/\b(rm|rmdir)\b/.test(cmd)) return '删除文件'
130
+ if (/\b(pnpm|npm|yarn|npx)\s+(run|exec|start|dev|build|test)\b/.test(cmd)) return '运行脚本'
131
+ if (/\b(node|ts-node|tsx)\b/.test(cmd)) return '运行脚本'
132
+ if (/\b(cat|head|tail|less|more)\b/.test(cmd)) return '查看文件'
133
+ if (/\bls\b/.test(cmd)) return '查看目录'
134
+ if (/\b(sed|awk|grep|find)\b/.test(cmd)) return '处理文件'
135
+ if (/\bscreencapture\b/.test(cmd)) return '截取屏幕'
136
+ if (/\bcurl|wget\b/.test(cmd)) return '网络请求'
137
+ if (/\bdocker\b/.test(cmd)) return '运行容器'
138
+ if (/\bcd\b/.test(cmd)) return '切换目录'
139
+ return '执行命令'
140
+ }
141
+ if (['read', 'read_file', 'grep', 'glob'].includes(toolName)) {
142
+ const p = String(inp.path ?? inp.pattern ?? '').split('/').pop() ?? ''
143
+ return `阅读 ${p || '文件'}`
144
+ }
145
+ if (['write', 'edit', 'write_file', 'edit_file'].includes(toolName)) {
146
+ const p = String(inp.path ?? inp.file ?? inp.content?.toString().slice(0, 0) ?? '').split('/').pop() ?? ''
147
+ const writePath = String(inp.path ?? inp.file ?? '').split('/').pop() ?? ''
148
+ return `编辑 ${writePath || '文件'}`
149
+ }
150
+ if (toolName === 'web_search') return '搜索网络'
151
+ if (toolName === 'web_fetch') return '访问网页'
152
+ if (toolName === 'browser') return '浏览器操作'
153
+ if (toolName === 'process') {
154
+ const action = String(inp.action ?? '')
155
+ if (action === 'poll') return '等待进程完成'
156
+ if (action === 'log') return '查看进程日志'
157
+ if (action === 'kill') return '终止进程'
158
+ return '进程操作'
159
+ }
160
+ if (toolName === 'skill') {
161
+ const sn = String(inp.skill_name ?? inp.name ?? '').slice(0, 20)
162
+ return `使用技能:${sn || '技能'}`
163
+ }
164
+ if (toolName === 'spawn_agent' || toolName === 'sessions_spawn') {
165
+ const name = String(inp.name ?? inp.displayName ?? inp.label ?? '').slice(0, 15)
166
+ return `召唤 ${name || '居民'}`
167
+ }
168
+ if (toolName === 'todo_write') return ''
169
+ return `使用工具:${toolName}`
170
+ }
171
+
172
+ isTodoWrite(toolName: string): boolean {
173
+ return toolName === 'todo_write'
174
+ }
175
+
176
+ /** Emit a todo list activity entry */
177
+ emitTodoActivity(npcId: string, input: Record<string, unknown>): void {
178
+ const todos = input.todos as Array<{ id: number; content: string; status: string }> | undefined
179
+ if (!todos || !Array.isArray(todos)) return
180
+ const ev: GameEvent = { type: 'npc_activity_todo', npcId, todos: todos.map(t => ({ id: t.id, content: t.content, status: t.status })) }
181
+ this.cacheActivity(ev)
182
+ this.emitFn([ev])
183
+ }
184
+ }
@@ -0,0 +1,280 @@
1
+ // @desc Citizen lifecycle: persona detection, spawn animation sequence, and name matching
2
+ import type { GameEvent } from '../../town-frontend/src/data/GameProtocol.js'
3
+ import type { RouteManager } from './RouteManager.js'
4
+ import { CITIZEN_SPAWN_ORIGIN, STEWARD_FACE_POS } from './RouteManager.js'
5
+ import { getCharacterKeyForNpc } from '../../town-frontend/src/data/CharacterRoster.js'
6
+
7
+ export interface CitizenManagerDeps {
8
+ emit: (events: GameEvent[]) => void
9
+ routes: RouteManager
10
+ getConfig: () => any
11
+ getCharacterAssignments: () => Map<string, string>
12
+ isStewardConfirmed: () => boolean
13
+ getPersonaName: () => string | null
14
+ getStewardName: () => string
15
+ onPersonaChanged: (name: string) => void
16
+ delayMs: (ms: number) => Promise<void>
17
+ scheduleDelayedEmit: (delayMs: number, events: GameEvent[]) => void
18
+ getLastToolInput: () => Record<string, unknown>
19
+ }
20
+
21
+ /** Detects new citizen persona files, manages the spawn queue, and orchestrates the citizen arrival animation (spawn -> greet steward -> walk to destination) */
22
+ export class CitizenManager {
23
+ pendingCitizenNames: { npcId: string; citizenName: string }[] = []
24
+ citizenSpawnQueue: { npcId: string; citizenName: string }[] = []
25
+ citizenSpawning = false
26
+ activeCitizenNpcs = new Set<string>()
27
+ spawnedCitizenCount = 0
28
+ spawnedCitizenIds = new Set<string>()
29
+ citizenFlowStartMs = new Map<string, number>()
30
+ pendingStewardRenameTimer: ReturnType<typeof setTimeout> | null = null
31
+
32
+ constructor(private deps: CitizenManagerDeps) {}
33
+
34
+ /** Handle switch_persona tool result: update steward name and trigger persona transform VFX */
35
+ async detectPersonaSwitch(event: any): Promise<void> {
36
+ if (event.name !== 'switch_persona') return
37
+ const output = String(event.output ?? '')
38
+ const m = output.match(/Persona switched to "(.+?)"/)
39
+ if (!m) return
40
+
41
+ const newName = m[1]
42
+
43
+ this.deps.emit([
44
+ { type: 'fx', effect: 'personaTransform', params: { npcId: this.deps.getStewardName() } },
45
+ ])
46
+
47
+ if (this.pendingStewardRenameTimer) {
48
+ clearTimeout(this.pendingStewardRenameTimer)
49
+ this.pendingStewardRenameTimer = null
50
+ }
51
+ this.pendingStewardRenameTimer = setTimeout(() => {
52
+ this.pendingStewardRenameTimer = null
53
+ this.deps.emit([
54
+ { type: 'steward_rename', npcId: 'steward', newName },
55
+ ])
56
+ }, 1500)
57
+
58
+ this.flushPendingCitizens()
59
+ this.deps.onPersonaChanged(newName)
60
+ }
61
+
62
+ /** Handle write_file tool result: if the file is a persona, queue the citizen for spawning */
63
+ detectCitizenCreated(event: any): void {
64
+ if (event.name !== 'write_file') return
65
+ const filePath = String(event.meta?.filePath ?? this.deps.getLastToolInput().path ?? '')
66
+ console.log('[CitizenManager] detectCitizenCreated filePath:', filePath, 'isPersona:', this.isPersonaPath(filePath))
67
+ if (!this.isPersonaPath(filePath)) return
68
+
69
+ const fileName = filePath.split('/').pop() ?? ''
70
+ const citizenName = fileName.replace(/\.md$/i, '').replace(/_/g, ' ').trim()
71
+ if (!citizenName) return
72
+
73
+ const npcId = `citizen_${citizenName.toLowerCase().replace(/\s+/g, '_')}`
74
+ if (this.spawnedCitizenIds.has(npcId)) return
75
+ if (this.pendingCitizenNames.some(p => p.npcId === npcId)) return
76
+ if (this.citizenSpawnQueue.some(p => p.npcId === npcId)) return
77
+
78
+ if (!this.deps.isStewardConfirmed()) {
79
+ console.log(`[CitizenManager] steward not confirmed yet, queue persona file: ${citizenName} (${npcId})`)
80
+ this.pendingCitizenNames.push({ npcId, citizenName })
81
+ return
82
+ }
83
+
84
+ if (this.isStewardName(citizenName)) {
85
+ console.log(`[CitizenManager] skipping recognized steward persona file: ${citizenName}`)
86
+ this.spawnedCitizenIds.add(npcId)
87
+ return
88
+ }
89
+
90
+ console.log(`[CitizenManager] citizen persona detected: ${citizenName} (${npcId}), spawn immediately`)
91
+ this.spawnedCitizenIds.add(npcId)
92
+ this.citizenSpawnQueue.push({ npcId, citizenName })
93
+ this.drainCitizenSpawnQueue()
94
+ }
95
+
96
+ isStewardName(name: string): boolean {
97
+ const lower = name.toLowerCase().replace(/\s+/g, '_')
98
+ const personaName = this.deps.getPersonaName()
99
+ if (personaName && lower === personaName.toLowerCase().replace(/\s+/g, '_')) return true
100
+ const stewardName = this.deps.getStewardName()
101
+ if (stewardName !== 'steward' && lower === stewardName.toLowerCase().replace(/\s+/g, '_')) return true
102
+ return false
103
+ }
104
+
105
+ /** Move pending citizen names (held until steward persona is confirmed) into the spawn queue */
106
+ flushPendingCitizens(): void {
107
+ if (!this.deps.isStewardConfirmed()) {
108
+ console.log('[CitizenManager] steward persona not confirmed yet, defer citizen spawning')
109
+ return
110
+ }
111
+
112
+ const toSpawn = this.pendingCitizenNames.splice(0)
113
+ for (const { npcId, citizenName } of toSpawn) {
114
+ if (this.spawnedCitizenIds.has(npcId)) continue
115
+ if (this.isStewardName(citizenName)) {
116
+ console.log(`[CitizenManager] skipping steward persona: ${citizenName}`)
117
+ this.spawnedCitizenIds.add(npcId)
118
+ continue
119
+ }
120
+ this.spawnedCitizenIds.add(npcId)
121
+ this.citizenSpawnQueue.push({ npcId, citizenName })
122
+ }
123
+ this.drainCitizenSpawnQueue()
124
+ }
125
+
126
+ drainCitizenSpawnQueue(): void {
127
+ while (this.citizenSpawnQueue.length > 0) {
128
+ if (this.citizenSpawning) return
129
+ const next = this.citizenSpawnQueue[0]
130
+ if (this.isStewardName(next.citizenName)) {
131
+ console.log(`[CitizenManager] drainQueue: skipping steward persona: ${next.citizenName}`)
132
+ this.citizenSpawnQueue.shift()
133
+ continue
134
+ }
135
+ this.citizenSpawning = true
136
+ this.citizenSpawnQueue.shift()
137
+ this.spawnCitizenSequence(next.npcId, next.citizenName)
138
+ return
139
+ }
140
+ }
141
+
142
+ async spawnCitizenSequence(npcId: string, citizenName: string): Promise<void> {
143
+ const idx = this.spawnedCitizenCount++
144
+
145
+ const spawnX = CITIZEN_SPAWN_ORIGIN.x + (idx % 3 - 1) * 2.5
146
+ const spawnZ = CITIZEN_SPAWN_ORIGIN.z
147
+ const flowStart = Date.now()
148
+ this.citizenFlowStartMs.set(npcId, flowStart)
149
+
150
+ console.log(`[CitizenManager] spawning citizen: ${citizenName} (${npcId}) at (${spawnX.toFixed(1)}, ${spawnZ.toFixed(1)})`)
151
+
152
+ this.activeCitizenNpcs.add(npcId)
153
+ let claimedDestinationId: string | null = null
154
+ let destinationArrived = false
155
+ const config = this.deps.getConfig()
156
+ const configuredCitizen = config?.citizens?.find((c: any) => c.id === npcId)
157
+ const citizenCharacterKey = configuredCitizen?.avatarId ?? getCharacterKeyForNpc(npcId)
158
+
159
+ this.deps.emit([
160
+ {
161
+ type: 'npc_spawn', npcId, name: citizenName,
162
+ role: 'general', category: 'citizen',
163
+ spawn: { x: spawnX, y: 0, z: spawnZ },
164
+ avatarId: citizenCharacterKey,
165
+ },
166
+ ])
167
+
168
+ try {
169
+ await this.deps.delayMs(300)
170
+
171
+ const greetPoint = this.deps.routes.getGreetPoint(idx)
172
+ const greetX = greetPoint.x
173
+ const greetZ = greetPoint.z
174
+ const distToSteward = Math.sqrt((greetX - spawnX) ** 2 + (greetZ - spawnZ) ** 2)
175
+ const walkSpeed = 5
176
+ const greetStart = Date.now()
177
+ console.log(`[CitizenManager][CitizenFlow] ${npcId} -> greet target=(${greetX.toFixed(2)}, ${greetZ.toFixed(2)}) speed=${walkSpeed} mode=arrival-driven`)
178
+
179
+ const greetStatus = await this.deps.routes.moveNpcAndWait(
180
+ npcId,
181
+ { x: greetX, y: 0, z: greetZ },
182
+ walkSpeed,
183
+ )
184
+ if (greetStatus !== 'arrived') {
185
+ console.warn(`[CitizenManager][CitizenFlow] ${npcId} greet move interrupted`)
186
+ return
187
+ }
188
+
189
+ this.deps.emit([
190
+ { type: 'npc_anim', npcId, anim: 'wave' },
191
+ { type: 'fx', effect: 'exclamation', params: { npcId } },
192
+ { type: 'dialog_message', npcId, text: `你好!我是${citizenName},很高兴来到小镇!`, isStreaming: false },
193
+ ])
194
+
195
+ await this.deps.delayMs(2500)
196
+
197
+ const destination = this.deps.routes.chooseCitizenDestination(npcId, { x: greetX, z: greetZ })
198
+ const targetX = destination.x + (Math.random() - 0.5) * 1.3
199
+ const targetZ = destination.z + (Math.random() - 0.5) * 1.3
200
+ this.deps.routes.claimDestinationForNpc(npcId, destination.id)
201
+ claimedDestinationId = destination.id
202
+ const distToDestination = this.deps.routes.distance2D({ x: greetX, z: greetZ }, { x: targetX, z: targetZ })
203
+ console.log(`[CitizenManager][CitizenFlow] ${npcId} route destination=${destination.id} target=(${targetX.toFixed(2)}, ${targetZ.toFixed(2)}) score=${destination.score.toFixed(2)} mode=arrival-driven`)
204
+
205
+ const destinationStatus = await this.deps.routes.moveNpcAndWait(
206
+ npcId,
207
+ { x: targetX, y: 0, z: targetZ },
208
+ walkSpeed,
209
+ )
210
+ if (destinationStatus !== 'arrived') {
211
+ console.warn(`[CitizenManager][CitizenFlow] ${npcId} destination move interrupted`)
212
+ return
213
+ }
214
+
215
+ destinationArrived = true
216
+ this.deps.emit([{ type: 'npc_daily_behavior_ready', npcId }])
217
+ this.deps.routes.releaseDestinationClaimLater(npcId, destination.id, 30000)
218
+ const flowEnd = Date.now()
219
+ console.log(`[CitizenManager][CitizenFlow] ${npcId} done total=${flowEnd - flowStart}ms`)
220
+ } finally {
221
+ if (claimedDestinationId && !destinationArrived) {
222
+ this.deps.routes.releaseDestinationClaim(npcId, claimedDestinationId)
223
+ }
224
+ this.activeCitizenNpcs.delete(npcId)
225
+ this.citizenFlowStartMs.delete(npcId)
226
+ this.citizenSpawning = false
227
+ this.drainCitizenSpawnQueue()
228
+ }
229
+ }
230
+
231
+ /** Find a citizen's NPC ID by exact name match against town config and spawned IDs */
232
+ findCitizenNpcId(name: string): string | null {
233
+ const normalized = name.toLowerCase().replace(/\s+/g, '_')
234
+ const config = this.deps.getConfig()
235
+
236
+ if (config?.citizens) {
237
+ for (const c of config.citizens) {
238
+ if (c.name === name || c.name.toLowerCase().replace(/\s+/g, '_') === normalized) {
239
+ return c.id
240
+ }
241
+ }
242
+ }
243
+
244
+ const candidateId = `citizen_${normalized}`
245
+ if (this.spawnedCitizenIds.has(candidateId)) return candidateId
246
+ for (const id of this.spawnedCitizenIds) {
247
+ if (id.endsWith(`_${normalized}`)) return id
248
+ }
249
+ return null
250
+ }
251
+
252
+ /** Find a citizen's NPC ID by fuzzy substring match */
253
+ fuzzyMatchCitizen(name: string): string | null {
254
+ const lower = name.toLowerCase().replace(/\s+/g, '')
255
+ if (!lower) return null
256
+ const config = this.deps.getConfig()
257
+
258
+ if (config?.citizens) {
259
+ for (const c of config.citizens) {
260
+ const cName = c.name.toLowerCase().replace(/\s+/g, '')
261
+ if (cName.includes(lower) || lower.includes(cName)) return c.id
262
+ }
263
+ }
264
+
265
+ for (const id of this.spawnedCitizenIds) {
266
+ const citizenName = id.replace(/^citizen_/, '').replace(/_/g, '')
267
+ if (citizenName.includes(lower) || lower.includes(citizenName)) return id
268
+ }
269
+ return null
270
+ }
271
+
272
+ looksLikeIdFragment(name: string): boolean {
273
+ return /^[a-z0-9_\- ]{1,12}$/i.test(name) && !/[\u4e00-\u9fa5]/.test(name)
274
+ }
275
+
276
+ isPersonaPath(filePath: string | null): boolean {
277
+ if (!filePath) return false
278
+ return /\bpersonas?\b/i.test(filePath)
279
+ }
280
+ }