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,142 @@
1
+ // @desc Lightweight AgentEvent → GameEvent translator used as fallback by DirectorBridge
2
+ import type { AgentEvent } from '../contracts/events.js'
3
+ import type { StateTracker } from './StateTracker.js'
4
+ import type { GameEvent } from '../../town-frontend/src/data/GameProtocol.js'
5
+
6
+ export type { GameEvent } from '../../town-frontend/src/data/GameProtocol.js'
7
+
8
+ const FILE_TOOLS = new Set(['read_file', 'write_file', 'edit_file'])
9
+
10
+ function extractFilePath(toolName: string, input: Record<string, unknown>): string | null {
11
+ if (FILE_TOOLS.has(toolName)) return (input.path ?? input.file) as string | null
12
+ if (toolName === 'bash') {
13
+ const cmd = String(input.command ?? '')
14
+ const m = cmd.match(/(?:cat|head|tail|vi|vim|nano|code)\s+["']?([^\s"']+)/)
15
+ return m ? m[1] : null
16
+ }
17
+ return null
18
+ }
19
+
20
+ /** Translates AgentEvent (from the agent protocol) into GameEvent[] (consumed by the 3D frontend). Used by DirectorBridge as a default/fallback handler for event types it doesn't process directly. */
21
+ export class EventTranslator {
22
+ private tracker: StateTracker
23
+
24
+ constructor(tracker: StateTracker) {
25
+ this.tracker = tracker
26
+ }
27
+
28
+ /** Main entry: switch on event type and return corresponding GameEvents */
29
+ translate(event: AgentEvent, contextNpcId?: string): GameEvent[] {
30
+ switch (event.type) {
31
+ case 'system':
32
+ return this.handleSystem(event)
33
+ case 'sub_agent':
34
+ return this.handleSubAgent(event)
35
+ case 'text_delta':
36
+ return [{ type: 'dialog_message', npcId: contextNpcId ?? this.tracker.stewardNpcId, text: event.delta, isStreaming: true }]
37
+ case 'text':
38
+ return [{ type: 'dialog_message', npcId: contextNpcId ?? this.tracker.stewardNpcId, text: event.content, isStreaming: false }]
39
+ case 'tool_use':
40
+ return this.handleToolUse(event, contextNpcId)
41
+ case 'tool_result':
42
+ return this.handleToolResult(event, contextNpcId)
43
+ case 'thinking_delta':
44
+ return contextNpcId
45
+ ? [{ type: 'npc_phase', npcId: contextNpcId, phase: 'thinking' }]
46
+ : [{ type: 'npc_phase', npcId: this.tracker.stewardNpcId, phase: 'thinking' }]
47
+ case 'turn_end':
48
+ return contextNpcId
49
+ ? [{ type: 'npc_phase', npcId: contextNpcId, phase: 'idle' }, { type: 'workstation_screen', stationId: '', state: { mode: 'off' } }]
50
+ : []
51
+ case 'bus_message': {
52
+ const fromNpc = this.tracker.resolveNpcId(event.from)
53
+ if (fromNpc) return [{ type: 'dialog_message', npcId: fromNpc, text: event.summary, isStreaming: false }]
54
+ return []
55
+ }
56
+ case 'world_control': {
57
+ if (event.target === 'time') {
58
+ return [{ type: 'set_time' as const, action: event.action, hour: event.hour }]
59
+ }
60
+ if (event.target === 'weather') {
61
+ return [{ type: 'set_weather' as const, action: event.action, weather: event.weather }]
62
+ }
63
+ return []
64
+ }
65
+ case 'error':
66
+ return contextNpcId
67
+ ? [{ type: 'npc_phase', npcId: contextNpcId, phase: 'error' }, { type: 'npc_emote', npcId: contextNpcId, emote: 'frustrated' }]
68
+ : []
69
+ default:
70
+ return []
71
+ }
72
+ }
73
+
74
+ private handleSystem(event: Extract<AgentEvent, { type: 'system' }>): GameEvent[] {
75
+ if (event.subtype === 'init') {
76
+ console.log('[EventTranslator] handleSystem init, sending world_init (NPCs already spawned by MainScene from town-defaults.json)')
77
+ return [
78
+ { type: 'world_init', config: { townName: '夏尔', stewardName: 'OpenClaw', citizenCount: 5 } },
79
+ ]
80
+ }
81
+ return []
82
+ }
83
+
84
+ private handleSubAgent(event: Extract<AgentEvent, { type: 'sub_agent' }>): GameEvent[] {
85
+ const { subtype, agentId } = event
86
+
87
+ if (subtype === 'started') {
88
+ const displayName = event.displayName ?? agentId
89
+ const npcId = agentId.replace(/^agent_/, '')
90
+ this.tracker.registerMapping(agentId, npcId)
91
+ const stationId = this.tracker.allocateStation()
92
+
93
+ const events: GameEvent[] = [
94
+ { type: 'npc_spawn', npcId, name: displayName, role: 'programming', category: 'citizen', task: event.task },
95
+ ]
96
+ if (stationId) {
97
+ this.tracker.setStationForNpc(npcId, stationId)
98
+ events.push({ type: 'workstation_assign', npcId, stationId })
99
+ }
100
+ return events
101
+ }
102
+
103
+ if (subtype === 'done') {
104
+ const npcId = this.tracker.resolveNpcId(agentId)
105
+ if (!npcId) return []
106
+ const status = event.status as string
107
+ const isError = status === 'failed'
108
+ this.tracker.removeMapping(agentId)
109
+ return [
110
+ { type: 'npc_phase', npcId, phase: isError ? 'error' : 'done' },
111
+ { type: 'npc_glow', npcId, color: isError ? 'red' : 'green' },
112
+ { type: 'fx', effect: isError ? 'error_sparks' : 'completion_stars', params: { npcId } },
113
+ ]
114
+ }
115
+
116
+ if (subtype === 'progress') {
117
+ const npcId = this.tracker.resolveNpcId(agentId)
118
+ if (!npcId) return []
119
+ return this.translate(event.event, npcId)
120
+ }
121
+
122
+ return []
123
+ }
124
+
125
+ private handleToolUse(event: Extract<AgentEvent, { type: 'tool_use' }>, contextNpcId?: string): GameEvent[] {
126
+ const npcId = contextNpcId ?? this.tracker.stewardNpcId
127
+ const filePath = extractFilePath(event.name, event.input)
128
+ const events: GameEvent[] = [{ type: 'npc_phase', npcId, phase: 'working' }]
129
+ if (filePath) {
130
+ const fileName = filePath.split('/').pop() ?? filePath
131
+ events.push({ type: 'workstation_screen', stationId: '', state: { mode: 'coding', fileName } })
132
+ }
133
+ return events
134
+ }
135
+
136
+ private handleToolResult(event: Extract<AgentEvent, { type: 'tool_result' }>, contextNpcId?: string): GameEvent[] {
137
+ if (event.meta?.filePath) {
138
+ return [{ type: 'workstation_screen', stationId: '', state: { mode: 'done' } }]
139
+ }
140
+ return []
141
+ }
142
+ }
@@ -0,0 +1,61 @@
1
+ // @desc Event queue that protects dialog bubbles from being overridden by rapid phase changes
2
+ import type { GameEvent } from '../../town-frontend/src/data/GameProtocol.js'
3
+
4
+ /** Calculate how long a dialog bubble should stay visible based on text length */
5
+ export function calcDialogDuration(textLength: number): number {
6
+ return Math.min(8000, Math.max(1500, textLength * 120))
7
+ }
8
+
9
+ /** Queue that serializes dialog and phase events per NPC, ensuring dialogs display long enough before phase changes take effect */
10
+ export class NpcEventQueue {
11
+ private pendingPhase: Array<{ events: GameEvent[] }> = []
12
+ private dialogProtected = false
13
+ private dialogTimer: ReturnType<typeof setTimeout> | null = null
14
+ private dialogTextLength = 0
15
+ private emitFn: (events: GameEvent[]) => void
16
+
17
+ constructor(emitFn: (events: GameEvent[]) => void) {
18
+ this.emitFn = emitFn
19
+ }
20
+
21
+ /** Emit dialog events immediately and protect the bubble for a duration proportional to text length */
22
+ enqueueDialog(events: GameEvent[], textLength: number): void {
23
+ this.emitFn(events)
24
+
25
+ this.dialogTextLength += textLength
26
+ this.dialogProtected = true
27
+
28
+ if (this.dialogTimer) clearTimeout(this.dialogTimer)
29
+ const duration = calcDialogDuration(this.dialogTextLength)
30
+ this.dialogTimer = setTimeout(() => {
31
+ this.dialogProtected = false
32
+ this.dialogTimer = null
33
+ this.dialogTextLength = 0
34
+ this.drainPending()
35
+ }, duration)
36
+ }
37
+
38
+ /** Emit phase events immediately if no dialog is protected, otherwise buffer until dialog expires */
39
+ enqueuePhase(events: GameEvent[]): void {
40
+ if (this.dialogProtected) {
41
+ this.pendingPhase.push({ events })
42
+ } else {
43
+ this.emitFn(events)
44
+ }
45
+ }
46
+
47
+ /** Cancel any active dialog protection and drain all pending phase events */
48
+ flush(): void {
49
+ if (this.dialogTimer) { clearTimeout(this.dialogTimer); this.dialogTimer = null }
50
+ this.dialogProtected = false
51
+ this.dialogTextLength = 0
52
+ this.drainPending()
53
+ }
54
+
55
+ private drainPending(): void {
56
+ const pending = this.pendingPhase.splice(0)
57
+ for (const p of pending) {
58
+ this.emitFn(p.events)
59
+ }
60
+ }
61
+ }
@@ -0,0 +1,42 @@
1
+ // @desc Exponential backoff reconnection manager for WebSocket connections
2
+ export type ReconnectCallback = () => void
3
+
4
+ /** Schedules reconnection attempts with exponential backoff, resettable on success */
5
+ export class ReconnectManager {
6
+ private attempt = 0
7
+ private maxDelay = 30_000
8
+ private timer: ReturnType<typeof setTimeout> | null = null
9
+ private onReconnect: ReconnectCallback
10
+
11
+ constructor(onReconnect: ReconnectCallback) {
12
+ this.onReconnect = onReconnect
13
+ }
14
+
15
+ /** Schedule the next reconnection attempt with exponential backoff */
16
+ scheduleReconnect(): void {
17
+ const delay = Math.min(1000 * Math.pow(2, this.attempt), this.maxDelay)
18
+ this.attempt++
19
+ console.log(`[ReconnectManager] Reconnecting in ${delay}ms (attempt ${this.attempt})`)
20
+ this.timer = setTimeout(() => {
21
+ this.onReconnect()
22
+ }, delay)
23
+ }
24
+
25
+ /** Reset attempt counter and cancel any pending reconnect timer */
26
+ onSuccess(): void {
27
+ this.attempt = 0
28
+ if (this.timer) {
29
+ clearTimeout(this.timer)
30
+ this.timer = null
31
+ }
32
+ }
33
+
34
+ /** Stop all reconnection attempts and reset state */
35
+ stop(): void {
36
+ if (this.timer) {
37
+ clearTimeout(this.timer)
38
+ this.timer = null
39
+ }
40
+ this.attempt = 0
41
+ }
42
+ }
@@ -0,0 +1,270 @@
1
+ // @desc A* pathfinding, NPC movement requests with ack/timeout, and destination claim management
2
+ import type { GameEvent } from '../../town-frontend/src/data/GameProtocol.js'
3
+ import { CITIZEN_DESTINATION_POINTS, ROUTE_GRAPHS, type RouteNode, type RouteSceneId } from './data/route-config.js'
4
+
5
+ export const CITIZEN_SPAWN_ORIGIN = { x: 20, z: 24 }
6
+ export const PLAZA_CENTER = { x: 18, z: 13 }
7
+ export const LEAVE_PLAZA_MIN_DISTANCE = 6.2
8
+ export const GREET_RING_RADIUS = 2.2
9
+ export const STEWARD_FACE_POS = { x: 17, z: 15.8 }
10
+
11
+ /** Manages NPC navigation: A* route planning on the town/office graph, move-and-wait with timeout, and destination slot allocation to prevent overcrowding */
12
+ export class RouteManager {
13
+ moveRequestSeq = 0
14
+ pendingMoveRequests = new Map<
15
+ string,
16
+ {
17
+ npcId: string
18
+ resolve: (status: 'arrived' | 'interrupted') => void
19
+ timer: ReturnType<typeof setTimeout>
20
+ }
21
+ >()
22
+ destinationClaimCount = new Map<string, number>()
23
+ npcDestinationClaim = new Map<string, string>()
24
+ npcLastDestination = new Map<string, string>()
25
+
26
+ private emitFn: (events: GameEvent[]) => void
27
+
28
+ constructor(emitFn: (events: GameEvent[]) => void) {
29
+ this.emitFn = emitFn
30
+ }
31
+
32
+ nextMoveRequestId(npcId: string): string {
33
+ this.moveRequestSeq += 1
34
+ return `${npcId}-move-${this.moveRequestSeq}`
35
+ }
36
+
37
+ /** Called by DirectorBridge when frontend reports NPC arrival or interruption */
38
+ resolveMoveRequest(requestId: string, npcId: string, status: 'arrived' | 'interrupted'): void {
39
+ if (!requestId) return
40
+ const pending = this.pendingMoveRequests.get(requestId)
41
+ if (!pending) return
42
+ clearTimeout(pending.timer)
43
+ this.pendingMoveRequests.delete(requestId)
44
+ if (pending.npcId !== npcId) {
45
+ console.warn(`[RouteManager][MoveAck] requestId=${requestId} npc mismatch expected=${pending.npcId} actual=${npcId}`)
46
+ }
47
+ pending.resolve(status)
48
+ }
49
+
50
+ /** Emit a move command and return a promise that resolves when the frontend acks arrival or timeout expires */
51
+ moveNpcAndWait(
52
+ npcId: string,
53
+ target: { x: number; y: number; z: number },
54
+ speed: number,
55
+ timeoutMs = 25000,
56
+ ): Promise<'arrived' | 'interrupted'> {
57
+ const requestId = this.nextMoveRequestId(npcId)
58
+ return new Promise<'arrived' | 'interrupted'>((resolve) => {
59
+ const timer = setTimeout(() => {
60
+ this.pendingMoveRequests.delete(requestId)
61
+ console.warn(`[RouteManager][MoveAck] timeout npc=${npcId} requestId=${requestId}`)
62
+ resolve('interrupted')
63
+ }, timeoutMs)
64
+
65
+ this.pendingMoveRequests.set(requestId, { npcId, resolve, timer })
66
+ this.emitFn([{ type: 'npc_move_to', npcId, target, speed, requestId }])
67
+ })
68
+ }
69
+
70
+ distance2D(a: { x: number; z: number }, b: { x: number; z: number }): number {
71
+ const dx = a.x - b.x
72
+ const dz = a.z - b.z
73
+ return Math.sqrt(dx * dx + dz * dz)
74
+ }
75
+
76
+ /** Calculate a position on the greeting ring around the steward for the Nth citizen */
77
+ getGreetPoint(idx: number): { x: number; z: number } {
78
+ const angle = (idx * 1.37) % (Math.PI * 2)
79
+ const r = GREET_RING_RADIUS + (Math.random() - 0.5) * 0.45
80
+ return {
81
+ x: STEWARD_FACE_POS.x + Math.cos(angle) * r,
82
+ z: STEWARD_FACE_POS.z + Math.sin(angle) * r,
83
+ }
84
+ }
85
+
86
+ computeLeavePlazaPoint(from: { x: number; z: number }): { x: number; z: number } {
87
+ let dx = from.x - PLAZA_CENTER.x
88
+ let dz = from.z - PLAZA_CENTER.z
89
+ const len = Math.sqrt(dx * dx + dz * dz)
90
+ if (len < 0.0001) {
91
+ const angle = Math.random() * Math.PI * 2
92
+ dx = Math.cos(angle)
93
+ dz = Math.sin(angle)
94
+ } else {
95
+ dx /= len
96
+ dz /= len
97
+ }
98
+ const targetDist = LEAVE_PLAZA_MIN_DISTANCE + 0.7 + Math.random() * 1.8
99
+ return {
100
+ x: PLAZA_CENTER.x + dx * targetDist,
101
+ z: PLAZA_CENTER.z + dz * targetDist,
102
+ }
103
+ }
104
+
105
+ /** Register that an NPC has claimed a destination slot */
106
+ claimDestinationForNpc(npcId: string, destinationId: string): void {
107
+ const prev = this.npcDestinationClaim.get(npcId)
108
+ if (prev === destinationId) return
109
+ if (prev) {
110
+ const prevCount = this.destinationClaimCount.get(prev) ?? 0
111
+ if (prevCount <= 1) this.destinationClaimCount.delete(prev)
112
+ else this.destinationClaimCount.set(prev, prevCount - 1)
113
+ }
114
+ this.npcDestinationClaim.set(npcId, destinationId)
115
+ this.destinationClaimCount.set(destinationId, (this.destinationClaimCount.get(destinationId) ?? 0) + 1)
116
+ this.npcLastDestination.set(npcId, destinationId)
117
+ }
118
+
119
+ /** Release a previously claimed destination slot */
120
+ releaseDestinationClaim(npcId: string, destinationId: string): void {
121
+ const current = this.npcDestinationClaim.get(npcId)
122
+ if (current !== destinationId) return
123
+ this.npcDestinationClaim.delete(npcId)
124
+ const count = this.destinationClaimCount.get(destinationId) ?? 0
125
+ if (count <= 1) this.destinationClaimCount.delete(destinationId)
126
+ else this.destinationClaimCount.set(destinationId, count - 1)
127
+ }
128
+
129
+ releaseDestinationClaimLater(npcId: string, destinationId: string, delayMs: number): void {
130
+ setTimeout(() => {
131
+ this.releaseDestinationClaim(npcId, destinationId)
132
+ }, delayMs)
133
+ }
134
+
135
+ /** Score and select the best destination point for a citizen, avoiding recently visited or overcrowded spots */
136
+ chooseCitizenDestination(npcId: string, from: { x: number; z: number }): { id: string; x: number; z: number; score: number } {
137
+ const last = this.npcLastDestination.get(npcId)
138
+ let best: { id: string; x: number; z: number; score: number } | null = null
139
+ const candidates = CITIZEN_DESTINATION_POINTS.filter((d) => {
140
+ const plazaDistance = this.distance2D({ x: d.x, z: d.z }, PLAZA_CENTER)
141
+ if (plazaDistance < LEAVE_PLAZA_MIN_DISTANCE) return false
142
+ return (this.destinationClaimCount.get(d.id) ?? 0) === 0
143
+ })
144
+ const pool = candidates.length > 0 ? candidates : CITIZEN_DESTINATION_POINTS
145
+
146
+ for (const d of pool) {
147
+ const plazaDistance = this.distance2D({ x: d.x, z: d.z }, PLAZA_CENTER)
148
+ if (plazaDistance < LEAVE_PLAZA_MIN_DISTANCE) continue
149
+ const distFromCurrent = this.distance2D(from, { x: d.x, z: d.z })
150
+ const claimCount = this.destinationClaimCount.get(d.id) ?? 0
151
+ const sameAsLastPenalty = d.id === last ? 8 : 0
152
+ const claimPenalty = claimCount * 6
153
+ const randomBonus = Math.random() * 2.5
154
+ const score = randomBonus - distFromCurrent * 0.42 - claimPenalty - sameAsLastPenalty
155
+ if (!best || score > best.score) {
156
+ best = { id: d.id, x: d.x, z: d.z, score }
157
+ }
158
+ }
159
+
160
+ if (best) return best
161
+ const fallback = CITIZEN_DESTINATION_POINTS[Math.floor(Math.random() * CITIZEN_DESTINATION_POINTS.length)]
162
+ return { id: fallback.id, x: fallback.x, z: fallback.z, score: -999 }
163
+ }
164
+
165
+ getRouteGraph(scene: RouteSceneId): Record<string, RouteNode> {
166
+ return ROUTE_GRAPHS[scene] ?? ROUTE_GRAPHS.town
167
+ }
168
+
169
+ inferRouteScene(from: { x: number; z: number }, to: { x: number; z: number }): RouteSceneId {
170
+ if (from.z <= 8.5 && to.z <= 8.5) return 'office'
171
+ return 'town'
172
+ }
173
+
174
+ isRouteDebugEnabled(): boolean {
175
+ try {
176
+ const g = globalThis as unknown as { location?: { search?: string }; localStorage?: { getItem?: (key: string) => string | null } }
177
+ const search = typeof g.location?.search === 'string'
178
+ ? new URLSearchParams(g.location.search)
179
+ : null
180
+ if (search?.get('routeDebug') === '1') return true
181
+ const local = g.localStorage?.getItem?.('agentshire_route_debug')
182
+ return local === '1' || local === 'true'
183
+ } catch {
184
+ return false
185
+ }
186
+ }
187
+
188
+ getNearestRouteNodeId(pos: { x: number; z: number }, scene: RouteSceneId): string {
189
+ const graph = this.getRouteGraph(scene)
190
+ const fallbackId = Object.keys(graph)[0] ?? 'plaza_c'
191
+ let bestId = fallbackId
192
+ let bestDist = Number.POSITIVE_INFINITY
193
+ for (const [id, node] of Object.entries(graph)) {
194
+ const d = this.distance2D(pos, node)
195
+ if (d < bestDist) {
196
+ bestDist = d
197
+ bestId = id
198
+ }
199
+ }
200
+ return bestId
201
+ }
202
+
203
+ planRouteNodePath(startId: string, endId: string, scene: RouteSceneId): string[] {
204
+ const graph = this.getRouteGraph(scene)
205
+ if (startId === endId) return [startId]
206
+ const open = new Set<string>([startId])
207
+ const cameFrom = new Map<string, string>()
208
+ const gScore = new Map<string, number>()
209
+ const fScore = new Map<string, number>()
210
+
211
+ const heuristic = (a: string, b: string): number =>
212
+ this.distance2D(graph[a], graph[b])
213
+
214
+ gScore.set(startId, 0)
215
+ fScore.set(startId, heuristic(startId, endId))
216
+
217
+ while (open.size > 0) {
218
+ let current: string | null = null
219
+ let bestF = Number.POSITIVE_INFINITY
220
+ for (const id of open) {
221
+ const f = fScore.get(id) ?? Number.POSITIVE_INFINITY
222
+ if (f < bestF) {
223
+ bestF = f
224
+ current = id
225
+ }
226
+ }
227
+ if (!current) break
228
+ if (current === endId) {
229
+ const path: string[] = [current]
230
+ while (cameFrom.has(current)) {
231
+ current = cameFrom.get(current)!
232
+ path.push(current)
233
+ }
234
+ path.reverse()
235
+ return path
236
+ }
237
+ open.delete(current)
238
+ const node = graph[current]
239
+ for (const neighbor of node.neighbors) {
240
+ const tentative = (gScore.get(current) ?? Number.POSITIVE_INFINITY)
241
+ + this.distance2D(node, graph[neighbor])
242
+ if (tentative < (gScore.get(neighbor) ?? Number.POSITIVE_INFINITY)) {
243
+ cameFrom.set(neighbor, current)
244
+ gScore.set(neighbor, tentative)
245
+ fScore.set(neighbor, tentative + heuristic(neighbor, endId))
246
+ open.add(neighbor)
247
+ }
248
+ }
249
+ }
250
+
251
+ return [startId, endId]
252
+ }
253
+
254
+ /** Plan a multi-waypoint route through the graph from source to destination */
255
+ planSceneRoute(scene: RouteSceneId, from: { x: number; z: number }, to: { x: number; z: number }): Array<{ x: number; z: number }> {
256
+ const graph = this.getRouteGraph(scene)
257
+ const start = this.getNearestRouteNodeId(from, scene)
258
+ const end = this.getNearestRouteNodeId(to, scene)
259
+ const nodePath = this.planRouteNodePath(start, end, scene)
260
+ const points: Array<{ x: number; z: number }> = []
261
+
262
+ for (const [idx, id] of nodePath.entries()) {
263
+ if (idx === 0 && this.distance2D(from, graph[id]) < 1.2) continue
264
+ if (idx === nodePath.length - 1 && this.distance2D(to, graph[id]) < 1.2) continue
265
+ points.push({ x: graph[id].x, z: graph[id].z })
266
+ }
267
+ points.push({ x: to.x, z: to.z })
268
+ return points
269
+ }
270
+ }
@@ -0,0 +1,100 @@
1
+ // @desc Bidirectional agentId ↔ npcId mapping and workstation allocation
2
+ export interface NPCState {
3
+ npcId: string
4
+ agentId: string
5
+ phase: string
6
+ stationId?: string
7
+ }
8
+
9
+ const WORKSTATION_IDS = ['B', 'C', 'F', 'G', 'D', 'E', 'H', 'A', 'I', 'J']
10
+
11
+ /** Maintains bidirectional agentId ↔ npcId mappings and allocates workstation slots for sub-agent NPCs */
12
+ export class StateTracker {
13
+ private agentToNpc = new Map<string, string>()
14
+ private npcToAgent = new Map<string, string>()
15
+ private npcStates = new Map<string, NPCState>()
16
+ private usedStations = new Set<string>()
17
+
18
+ readonly stewardNpcId = 'steward'
19
+
20
+ /** Resolve an agentId to its corresponding npcId */
21
+ resolveNpcId(agentId: string): string | undefined {
22
+ return this.agentToNpc.get(agentId)
23
+ }
24
+
25
+ resolveAgentId(npcId: string): string | undefined {
26
+ return this.npcToAgent.get(npcId)
27
+ }
28
+
29
+ getStationForNpc(npcId: string): string | undefined {
30
+ return this.npcStates.get(npcId)?.stationId
31
+ }
32
+
33
+ /** Register a bidirectional agentId ↔ npcId mapping */
34
+ registerMapping(agentId: string, npcId: string): void {
35
+ this.agentToNpc.set(agentId, npcId)
36
+ this.npcToAgent.set(npcId, agentId)
37
+ }
38
+
39
+ /** Remove a mapping and associated NPC state by agentId */
40
+ removeMapping(agentId: string): void {
41
+ const npcId = this.agentToNpc.get(agentId)
42
+ if (npcId) {
43
+ const state = this.npcStates.get(npcId)
44
+ if (state?.stationId) {
45
+ this.usedStations.delete(state.stationId)
46
+ }
47
+ this.npcToAgent.delete(npcId)
48
+ this.npcStates.delete(npcId)
49
+ }
50
+ this.agentToNpc.delete(agentId)
51
+ }
52
+
53
+ removeMappingByNpcId(npcId: string): void {
54
+ const agentId = this.npcToAgent.get(npcId)
55
+ if (agentId) {
56
+ this.removeMapping(agentId)
57
+ }
58
+ }
59
+
60
+ /** Allocate the next available workstation ID, or null if all are in use */
61
+ allocateStation(): string | null {
62
+ for (const id of WORKSTATION_IDS) {
63
+ if (!this.usedStations.has(id)) {
64
+ this.usedStations.add(id)
65
+ return id
66
+ }
67
+ }
68
+ return null
69
+ }
70
+
71
+ releaseStation(stationId: string): void {
72
+ this.usedStations.delete(stationId)
73
+ }
74
+
75
+ updatePhase(npcId: string, phase: string): void {
76
+ const state = this.npcStates.get(npcId)
77
+ if (state) state.phase = phase
78
+ }
79
+
80
+ setStationForNpc(npcId: string, stationId: string): void {
81
+ let state = this.npcStates.get(npcId)
82
+ if (!state) {
83
+ state = { npcId, agentId: this.npcToAgent.get(npcId) ?? '', phase: 'idle' }
84
+ this.npcStates.set(npcId, state)
85
+ }
86
+ state.stationId = stationId
87
+ }
88
+
89
+ getAllNpcStates(): NPCState[] {
90
+ return [...this.npcStates.values()]
91
+ }
92
+
93
+ /** Reset all mappings, NPC states, and station allocations */
94
+ clear(): void {
95
+ this.agentToNpc.clear()
96
+ this.npcToAgent.clear()
97
+ this.npcStates.clear()
98
+ this.usedStations.clear()
99
+ }
100
+ }