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,680 @@
1
+ import { WebSocketServer, WebSocket } from "ws";
2
+ import type { AgentEvent } from "../contracts/events.js";
3
+ import { sanitizeTownSessionId } from "./town-session.js";
4
+ import { getActivityLogForAgent, type ActivityLogEntry } from "./subagent-tracker.js";
5
+ import type { CustomAssetManager } from "./custom-asset-manager.js";
6
+ import { loadChatHistory, loadNewMessages, getCurrentSessionId, invalidateSessionCache, loadCitizenHistory, loadCitizenNewMessages, loadSubagentFinalMessage, loadChatItemHistory, loadCitizenItemHistory } from "./session-history.js";
7
+ import { ChatSessionWatcher } from "./chat-session-watcher.js";
8
+ import { existsSync, readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { stateDir } from "./paths.js";
12
+
13
+ export interface TownWsServerOptions {
14
+ port: number;
15
+ customAssetManager?: CustomAssetManager;
16
+ onAction?: (payload: {
17
+ action: Record<string, unknown>;
18
+ townSessionId: string;
19
+ }) => void;
20
+ onChat?: (payload: { message: string; townSessionId: string }) => void;
21
+ onMultimodal?: (payload: {
22
+ parts: Array<{ kind: string; text?: string; data?: string; mimeType?: string; fileName?: string }>;
23
+ townSessionId: string;
24
+ agentId?: string;
25
+ npcId?: string;
26
+ }) => void;
27
+ onImplicitChat?: (payload: {
28
+ id: string;
29
+ system: string;
30
+ user: string;
31
+ maxTokens: number;
32
+ temperature: number;
33
+ stop: string[];
34
+ }) => Promise<{ text: string; usage?: { input: number; output: number } }>;
35
+ onCitizenChat?: (payload: { npcId: string; message: string; townSessionId: string }) => void;
36
+ onTopicStart?: (payload: { npcIds: string[]; townSessionId: string }) => void;
37
+ onTopicMessage?: (payload: { npcIds: string[]; message: string; townSessionId: string }) => void;
38
+ onTopicEnd?: (payload: { townSessionId: string }) => void;
39
+ }
40
+
41
+ let wss: WebSocketServer | null = null;
42
+ const clients = new Set<WebSocket>();
43
+ const clientSessions = new Map<WebSocket, string>();
44
+ const clientChatWatchers = new Map<WebSocket, ChatSessionWatcher>();
45
+ const clientChatBindings = new Map<WebSocket, { townSessionId: string; agentId: string }>();
46
+ const clientChatRetryTimers = new Map<WebSocket, ReturnType<typeof setTimeout>>();
47
+ const coldStartBindings = new Set<WebSocket>();
48
+ let activeTownSessionId: string | undefined;
49
+ let customAssetMgr: CustomAssetManager | undefined;
50
+
51
+ function getPublishedConfigPath(): string {
52
+ const pluginDir = join(fileURLToPath(import.meta.url), "..", "..", "..");
53
+ return join(pluginDir, "town-data", "citizen-config.json");
54
+ }
55
+
56
+ export function findCitizenNpcId(agentId: string): string | null {
57
+ return findCitizenNpcIdByAgentId(agentId);
58
+ }
59
+
60
+ function findCitizenNpcIdByAgentId(agentId: string): string | null {
61
+ try {
62
+ const configPath = getPublishedConfigPath();
63
+ if (!existsSync(configPath)) return null;
64
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
65
+ const characters: any[] = config.characters ?? [];
66
+ const citizen = characters.find(
67
+ (entry: any) => entry?.role === "citizen" && entry?.agentEnabled && entry?.agentId === agentId,
68
+ );
69
+ return typeof citizen?.id === "string" && citizen.id ? citizen.id : null;
70
+ } catch {
71
+ return null;
72
+ }
73
+ }
74
+
75
+ export function getActiveTownSessionId(): string | undefined {
76
+ return activeTownSessionId;
77
+ }
78
+
79
+ interface WorkSnapshotAgent {
80
+ id: string;
81
+ displayName: string;
82
+ task: string;
83
+ status: "pending" | "working" | "completed" | "failed";
84
+ }
85
+
86
+ interface WorkSnapshot {
87
+ phase: "working";
88
+ stewardPersona?: string;
89
+ agents: WorkSnapshotAgent[];
90
+ }
91
+
92
+ const workSnapshots = new Map<string, WorkSnapshot>();
93
+
94
+ function sessionLogPrefix(townSessionId: string): string {
95
+ return `[agentshire][session:${townSessionId}]`;
96
+ }
97
+
98
+ function cloneWorkSnapshot(snapshot: WorkSnapshot): WorkSnapshot {
99
+ return {
100
+ phase: snapshot.phase,
101
+ stewardPersona: snapshot.stewardPersona,
102
+ agents: snapshot.agents.map((agent) => ({ ...agent })),
103
+ };
104
+ }
105
+
106
+ function normalizeDoneStatus(status: string): WorkSnapshotAgent["status"] {
107
+ if (status === "completed") return "completed";
108
+ return "failed";
109
+ }
110
+
111
+ function getWorkSnapshot(townSessionId: string): WorkSnapshot | null {
112
+ return workSnapshots.get(townSessionId) ?? null;
113
+ }
114
+
115
+ function setWorkSnapshot(townSessionId: string, snapshot: WorkSnapshot | null): void {
116
+ if (!snapshot) {
117
+ workSnapshots.delete(townSessionId);
118
+ return;
119
+ }
120
+ workSnapshots.set(townSessionId, snapshot);
121
+ }
122
+
123
+ function updateWorkSnapshot(townSessionId: string, event: AgentEvent): void {
124
+ switch (event.type) {
125
+ case "system":
126
+ if (event.subtype === "init") {
127
+ const existing = getWorkSnapshot(townSessionId);
128
+ if (!existing?.agents.some(a => a.status === "working")) {
129
+ setWorkSnapshot(townSessionId, null);
130
+ }
131
+ } else if (event.subtype === "done") {
132
+ setWorkSnapshot(townSessionId, null);
133
+ }
134
+ return;
135
+
136
+ case "sub_agent":
137
+ if (event.subtype === "started") {
138
+ const snapshot = getWorkSnapshot(townSessionId)
139
+ ? cloneWorkSnapshot(getWorkSnapshot(townSessionId)!)
140
+ : { phase: "working" as const, agents: [] };
141
+ const existingIdx = snapshot.agents.findIndex((agent) => agent.id === event.agentId);
142
+ const nextAgent: WorkSnapshotAgent = {
143
+ id: event.agentId,
144
+ displayName: event.displayName ?? event.agentId,
145
+ task: event.task,
146
+ status: "working",
147
+ };
148
+ if (existingIdx >= 0) snapshot.agents[existingIdx] = nextAgent;
149
+ else snapshot.agents.push(nextAgent);
150
+ setWorkSnapshot(townSessionId, snapshot);
151
+ return;
152
+ }
153
+
154
+ if (event.subtype === "done" && getWorkSnapshot(townSessionId)) {
155
+ const snapshot = cloneWorkSnapshot(getWorkSnapshot(townSessionId)!);
156
+ const agent = snapshot.agents.find((item) => item.id === event.agentId);
157
+ if (!agent) return;
158
+ agent.status = normalizeDoneStatus(event.status);
159
+ setWorkSnapshot(townSessionId, snapshot);
160
+ }
161
+ return;
162
+
163
+ default:
164
+ return;
165
+ }
166
+ }
167
+
168
+ function getClientSessionId(ws: WebSocket): string {
169
+ return clientSessions.get(ws) ?? "default";
170
+ }
171
+
172
+ function sendWorkSnapshot(ws: WebSocket, townSessionId: string): void {
173
+ const snapshot = getWorkSnapshot(townSessionId);
174
+ if (ws.readyState !== WebSocket.OPEN) return;
175
+
176
+ let enrichedSnapshot: any = snapshot ?? null;
177
+ if (snapshot?.agents.length) {
178
+ enrichedSnapshot = {
179
+ ...snapshot,
180
+ agents: snapshot.agents.map((agent) => ({
181
+ ...agent,
182
+ activityLog: getActivityLogForAgent(agent.id),
183
+ })),
184
+ };
185
+ }
186
+
187
+ ws.send(
188
+ JSON.stringify({
189
+ type: "work_snapshot",
190
+ townSessionId,
191
+ snapshot: enrichedSnapshot,
192
+ }),
193
+ );
194
+ if (snapshot?.agents.length) {
195
+ console.log(
196
+ `[agentshire] Sent work snapshot (${snapshot.agents.length} agents) for ${townSessionId}`,
197
+ );
198
+ }
199
+ }
200
+
201
+ function handleCustomAssetMessage(ws: WebSocket, msg: any): boolean {
202
+ if (!customAssetMgr) return false;
203
+ const send = (data: Record<string, unknown>) => {
204
+ if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(data));
205
+ };
206
+
207
+ switch (msg.type) {
208
+ case "custom_asset_list": {
209
+ const assets = customAssetMgr.listAssets(msg.kind);
210
+ send({ type: "custom_asset_catalog", assets });
211
+ return true;
212
+ }
213
+ case "custom_asset_upload": {
214
+ const result = customAssetMgr.saveAsset({
215
+ kind: msg.kind ?? "model",
216
+ name: msg.name ?? "未命名",
217
+ data: msg.data,
218
+ cells: msg.cells,
219
+ scale: msg.scale,
220
+ assetType: msg.assetType,
221
+ fixRotationX: msg.fixRotationX,
222
+ fixRotationY: msg.fixRotationY,
223
+ fixRotationZ: msg.fixRotationZ,
224
+ thumbnail: msg.thumbnail,
225
+ });
226
+ if ("error" in result) {
227
+ send({ type: "custom_asset_error", message: result.error });
228
+ } else {
229
+ send({ type: "custom_asset_saved", asset: result });
230
+ }
231
+ return true;
232
+ }
233
+ case "custom_asset_update": {
234
+ const result = customAssetMgr.updateAsset(msg.id, {
235
+ name: msg.name,
236
+ cells: msg.cells,
237
+ scale: msg.scale,
238
+ assetType: msg.assetType,
239
+ fixRotationX: msg.fixRotationX,
240
+ fixRotationY: msg.fixRotationY,
241
+ fixRotationZ: msg.fixRotationZ,
242
+ thumbnail: msg.thumbnail,
243
+ });
244
+ if ("error" in result) {
245
+ send({ type: "custom_asset_error", message: result.error });
246
+ } else {
247
+ send({ type: "custom_asset_saved", asset: result });
248
+ }
249
+ return true;
250
+ }
251
+ case "custom_asset_delete": {
252
+ const result = customAssetMgr.deleteAsset(msg.id);
253
+ if (result.success) {
254
+ send({ type: "custom_asset_deleted", id: msg.id });
255
+ } else {
256
+ send({ type: "custom_asset_error", message: result.error ?? "删除失败" });
257
+ }
258
+ return true;
259
+ }
260
+ default:
261
+ return false;
262
+ }
263
+ }
264
+
265
+ function resolveChatTranscriptPath(townSessionId: string, agentId: string): string | null {
266
+ try {
267
+ if (agentId === "steward") {
268
+ const storeDir = join(stateDir(), "agents", "town-steward", "sessions");
269
+ const indexPath = join(storeDir, "sessions.json");
270
+ if (!existsSync(indexPath)) return null;
271
+ const index = JSON.parse(readFileSync(indexPath, "utf-8"));
272
+ const newKey = `agent:town-steward:town:default:${townSessionId}`;
273
+ const legacyKey = `town:default:${townSessionId}`;
274
+ const entry = (index[newKey] ?? index[legacyKey]) as any;
275
+ if (!entry?.sessionId) return null;
276
+ const fp = join(storeDir, `${entry.sessionId}.jsonl`);
277
+ return existsSync(fp) ? fp : null;
278
+ }
279
+ const storeDir = join(stateDir(), "agents", agentId, "sessions");
280
+ const indexPath = join(storeDir, "sessions.json");
281
+ if (!existsSync(indexPath)) return null;
282
+ const index = JSON.parse(readFileSync(indexPath, "utf-8"));
283
+ const exactKey = `agent:${agentId}:${townSessionId}`;
284
+ const exactEntry = index[exactKey] as any;
285
+ if (exactEntry?.sessionId) {
286
+ const fp = join(storeDir, `${exactEntry.sessionId}.jsonl`);
287
+ if (existsSync(fp)) return fp;
288
+ }
289
+ return null;
290
+ } catch {
291
+ return null;
292
+ }
293
+ }
294
+
295
+ function clearChatWatcherRetry(ws: WebSocket): void {
296
+ const timer = clientChatRetryTimers.get(ws);
297
+ if (timer) {
298
+ clearTimeout(timer);
299
+ clientChatRetryTimers.delete(ws);
300
+ }
301
+ }
302
+
303
+ function scheduleChatWatcherRetry(ws: WebSocket): void {
304
+ clearChatWatcherRetry(ws);
305
+ const binding = clientChatBindings.get(ws);
306
+ if (!binding) return;
307
+ const timer = setTimeout(() => {
308
+ clientChatRetryTimers.delete(ws);
309
+ const current = clientChatBindings.get(ws);
310
+ if (!current || clientChatWatchers.has(ws)) return;
311
+ tryStartChatWatcher(ws, current.townSessionId, current.agentId);
312
+ }, 400);
313
+ clientChatRetryTimers.set(ws, timer);
314
+ }
315
+
316
+ function tryStartChatWatcher(ws: WebSocket, townSessionId: string, agentId: string): boolean {
317
+ const transcriptPath = resolveChatTranscriptPath(townSessionId, agentId);
318
+ if (!transcriptPath) {
319
+ console.log(`${sessionLogPrefix(townSessionId)} Chat watcher: no transcript yet for ${agentId}`);
320
+ coldStartBindings.add(ws);
321
+ scheduleChatWatcherRetry(ws);
322
+ return false;
323
+ }
324
+ clearChatWatcherRetry(ws);
325
+ const isColdStart = coldStartBindings.has(ws);
326
+ coldStartBindings.delete(ws);
327
+ const existing = clientChatWatchers.get(ws);
328
+ if (existing) existing.stop();
329
+ const watcher = new ChatSessionWatcher(transcriptPath, agentId, (items) => {
330
+ if (ws.readyState === WebSocket.OPEN) {
331
+ ws.send(JSON.stringify({ type: "chat_delta", agentId, items }));
332
+ }
333
+ });
334
+ clientChatWatchers.set(ws, watcher);
335
+ watcher.start(isColdStart);
336
+ console.log(`${sessionLogPrefix(townSessionId)} Chat watcher started for ${agentId} (coldStart=${isColdStart})`);
337
+ return true;
338
+ }
339
+
340
+ export function retryChatWatchersForBinding(townSessionId: string, agentId: string): void {
341
+ for (const [ws, binding] of clientChatBindings) {
342
+ if (binding.townSessionId !== townSessionId || binding.agentId !== agentId) continue;
343
+ const existing = clientChatWatchers.get(ws);
344
+ if (existing) continue;
345
+ tryStartChatWatcher(ws, townSessionId, agentId);
346
+ }
347
+ }
348
+
349
+ export function startTownWsServer(opts: TownWsServerOptions): void {
350
+ if (wss) return;
351
+ customAssetMgr = opts.customAssetManager;
352
+
353
+ wss = new WebSocketServer({ port: opts.port });
354
+ console.log(`[agentshire] WebSocket server listening on ws://localhost:${opts.port}`);
355
+
356
+ wss.on("error", (err: NodeJS.ErrnoException) => {
357
+ if (err.code === "EADDRINUSE") {
358
+ console.error(`[agentshire] ❌ WebSocket port ${opts.port} is already in use.`);
359
+ console.error(`[agentshire] Fix: stop the process using this port, or change wsPort in openclaw.json:`);
360
+ console.error(`[agentshire] { "plugins": { "entries": { "agentshire": { "config": { "wsPort": ${opts.port + 1} } } } } }`);
361
+ } else {
362
+ console.error("[agentshire] WebSocket server error:", err);
363
+ }
364
+ });
365
+
366
+ wss.on("connection", (ws) => {
367
+ clients.add(ws);
368
+ console.log(`[agentshire] Town frontend connected (${clients.size} total)`);
369
+
370
+ ws.on("message", (raw) => {
371
+ try {
372
+ const msg = JSON.parse(String(raw));
373
+
374
+ if (msg.type === "town_session_init" && typeof msg.townSessionId === "string") {
375
+ const townSessionId = sanitizeTownSessionId(msg.townSessionId);
376
+ clientSessions.set(ws, townSessionId);
377
+ activeTownSessionId = townSessionId;
378
+ console.log(`${sessionLogPrefix(townSessionId)} WS bound to frontend connection`);
379
+ if (ws.readyState === WebSocket.OPEN) {
380
+ let modelName: string | undefined;
381
+ try {
382
+ const { getTownRuntime } = require("./runtime.js") as typeof import("./runtime.js");
383
+ const rt = getTownRuntime();
384
+ const cfg = typeof (rt.config as any)?.loadConfig === "function" ? (rt.config as any).loadConfig() : rt.config;
385
+ modelName = cfg?.agents?.defaults?.model?.primary;
386
+ } catch {}
387
+ ws.send(JSON.stringify({ type: "town_session_bound", townSessionId, ...(modelName ? { model: modelName } : {}) }));
388
+ }
389
+ sendWorkSnapshot(ws, townSessionId);
390
+ } else if (msg.type === "chat_agent_bind" && typeof msg.agentId === "string") {
391
+ const townSessionId = getClientSessionId(ws);
392
+ const agentId = msg.agentId;
393
+ console.log(`${sessionLogPrefix(townSessionId)} WS ← chat_agent_bind agentId=${agentId}`);
394
+
395
+ const oldWatcher = clientChatWatchers.get(ws);
396
+ if (oldWatcher) { oldWatcher.stop(); clientChatWatchers.delete(ws); }
397
+ clearChatWatcherRetry(ws);
398
+
399
+ clientChatBindings.set(ws, { townSessionId, agentId });
400
+
401
+ tryStartChatWatcher(ws, townSessionId, agentId);
402
+ } else if (msg.type === "chat") {
403
+ const townSessionId = getClientSessionId(ws);
404
+ console.log(
405
+ `${sessionLogPrefix(townSessionId)} WS ← chat ${JSON.stringify(msg).slice(0, 200)}`,
406
+ );
407
+ // DirectorBridge sends { type:'chat', body:[{kind:'text',text:'...'}] }
408
+ // InputBar may also send { type:'chat', message:'...' }
409
+ if (Array.isArray(msg.body)) {
410
+ const textParts = msg.body
411
+ .filter((p: any) => p.kind === "text" && p.text)
412
+ .map((p: any) => p.text)
413
+ .join(" ");
414
+ if (textParts) opts.onChat?.({ message: textParts, townSessionId });
415
+ } else if (typeof msg.message === "string") {
416
+ opts.onChat?.({ message: msg.message, townSessionId });
417
+ }
418
+ } else if (msg.type === "multimodal" && Array.isArray(msg.parts)) {
419
+ const townSessionId = getClientSessionId(ws);
420
+ console.log(
421
+ `${sessionLogPrefix(townSessionId)} WS ← multimodal parts=${msg.parts.length}`,
422
+ );
423
+ opts.onMultimodal?.({
424
+ parts: msg.parts,
425
+ townSessionId,
426
+ ...(typeof msg.agentId === "string" ? { agentId: msg.agentId } : {}),
427
+ ...(typeof msg.npcId === "string" ? { npcId: msg.npcId } : {}),
428
+ });
429
+ } else if (handleCustomAssetMessage(ws, msg)) {
430
+ // handled by custom asset manager
431
+ } else if (msg.type === "citizen_chat" && typeof msg.npcId === "string" && typeof msg.message === "string") {
432
+ const townSessionId = getClientSessionId(ws);
433
+ const _debug = process.env.AGENTSHIRE_DEBUG === "1";
434
+ console.log(
435
+ `${sessionLogPrefix(townSessionId)} WS ← citizen_chat npc=${msg.npcId} len=${String(msg.message).length}${_debug ? ` "${String(msg.message).slice(0, 80)}"` : ""}`,
436
+ );
437
+ opts.onCitizenChat?.({ npcId: msg.npcId, message: msg.message, townSessionId });
438
+ } else if (msg.type === "topic_start" && Array.isArray(msg.npcIds)) {
439
+ const townSessionId = getClientSessionId(ws);
440
+ console.log(`${sessionLogPrefix(townSessionId)} WS ← topic_start npcIds=[${msg.npcIds.join(",")}]`);
441
+ opts.onTopicStart?.({ npcIds: msg.npcIds, townSessionId });
442
+ } else if (msg.type === "topic_message" && Array.isArray(msg.npcIds) && typeof msg.message === "string") {
443
+ const townSessionId = getClientSessionId(ws);
444
+ console.log(`${sessionLogPrefix(townSessionId)} WS ← topic_message npcIds=[${msg.npcIds.join(",")}] len=${msg.message.length}`);
445
+ opts.onTopicMessage?.({ npcIds: msg.npcIds, message: msg.message, townSessionId });
446
+ } else if (msg.type === "topic_end") {
447
+ const townSessionId = getClientSessionId(ws);
448
+ console.log(`${sessionLogPrefix(townSessionId)} WS ← topic_end`);
449
+ opts.onTopicEnd?.({ townSessionId });
450
+ } else if (msg.type === "implicit_chat_request" && typeof msg.id === "string" && opts.onImplicitChat) {
451
+ const townSessionId = getClientSessionId(ws);
452
+ opts.onImplicitChat({
453
+ id: msg.id,
454
+ system: String(msg.system ?? ""),
455
+ user: String(msg.user ?? ""),
456
+ maxTokens: Number(msg.maxTokens ?? 200),
457
+ temperature: Number(msg.temperature ?? 0.85),
458
+ stop: Array.isArray(msg.stop) ? msg.stop.map(String) : [],
459
+ }).then((result) => {
460
+ if (ws.readyState === WebSocket.OPEN) {
461
+ ws.send(JSON.stringify({ type: "implicit_chat_response", id: msg.id, text: result.text, usage: result.usage }));
462
+ }
463
+ }).catch((err) => {
464
+ console.warn(`${sessionLogPrefix(townSessionId)} implicit_chat error:`, (err as Error).message);
465
+ if (ws.readyState === WebSocket.OPEN) {
466
+ ws.send(JSON.stringify({ type: "implicit_chat_response", id: msg.id, text: "", error: (err as Error).message }));
467
+ }
468
+ });
469
+ } else if (msg.type === "chat_history_request") {
470
+ const townSessionId = getClientSessionId(ws);
471
+ const limit = typeof msg.limit === "number" ? msg.limit : 50;
472
+ const cursor = typeof msg.cursor === "string" ? msg.cursor : undefined;
473
+ const agentId = typeof msg.agentId === "string" ? msg.agentId : undefined;
474
+ const format = typeof msg.format === "string" ? msg.format : "messages";
475
+ console.log(`${sessionLogPrefix(townSessionId)} WS ← chat_history_request agentId=${agentId ?? "steward"} limit=${limit} format=${format} cursor=${cursor ?? "latest"}`);
476
+
477
+ if (format === "items") {
478
+ const result = (agentId && agentId !== "steward")
479
+ ? loadCitizenItemHistory(agentId, limit)
480
+ : loadChatItemHistory(limit, cursor);
481
+ if (ws.readyState === WebSocket.OPEN) {
482
+ ws.send(JSON.stringify({
483
+ type: "chat_history",
484
+ agentId: agentId ?? "steward",
485
+ format: "items",
486
+ items: result.items,
487
+ hasMore: result.hasMore,
488
+ cursor: result.cursor,
489
+ }));
490
+ }
491
+ } else {
492
+ const result = (agentId && agentId !== "steward")
493
+ ? loadCitizenHistory(agentId, limit)
494
+ : loadChatHistory(limit, cursor);
495
+ if (ws.readyState === WebSocket.OPEN) {
496
+ ws.send(JSON.stringify({
497
+ type: "chat_history",
498
+ agentId: agentId ?? "steward",
499
+ messages: result.messages,
500
+ hasMore: result.hasMore,
501
+ cursor: result.cursor,
502
+ }));
503
+ }
504
+ }
505
+ } else if (msg.type === "command" && typeof msg.command === "string") {
506
+ const townSessionId = getClientSessionId(ws);
507
+ const slashText = `/${msg.command}${msg.args ? " " + msg.args : ""}`;
508
+ console.log(`${sessionLogPrefix(townSessionId)} WS ← command "${slashText}"`);
509
+ opts.onChat?.({ message: slashText, townSessionId });
510
+ } else if (msg.type === "abort") {
511
+ const townSessionId = getClientSessionId(ws);
512
+ console.log(`${sessionLogPrefix(townSessionId)} WS ← abort`);
513
+ opts.onAction?.({
514
+ action: { type: "abort_requested" },
515
+ townSessionId,
516
+ });
517
+ } else {
518
+ const townSessionId = getClientSessionId(ws);
519
+ console.log(
520
+ `${sessionLogPrefix(townSessionId)} WS ← action type=${String(msg?.type ?? "unknown")}`,
521
+ );
522
+ opts.onAction?.({ action: msg, townSessionId });
523
+ }
524
+ } catch (err) {
525
+ console.error("[agentshire] WS message parse error:", err);
526
+ }
527
+ });
528
+
529
+ ws.on("close", () => {
530
+ const watcher = clientChatWatchers.get(ws);
531
+ if (watcher) { watcher.stop(); clientChatWatchers.delete(ws); }
532
+ clearChatWatcherRetry(ws);
533
+ coldStartBindings.delete(ws);
534
+ clientChatBindings.delete(ws);
535
+ clientSessions.delete(ws);
536
+ clients.delete(ws);
537
+ console.log(`[agentshire] Town frontend disconnected (${clients.size} remaining)`);
538
+ });
539
+ });
540
+ }
541
+
542
+ export function stopTownWsServer(): void {
543
+ if (!wss) return;
544
+ for (const watcher of clientChatWatchers.values()) watcher.stop();
545
+ clientChatWatchers.clear();
546
+ for (const timer of clientChatRetryTimers.values()) clearTimeout(timer);
547
+ clientChatRetryTimers.clear();
548
+ clientChatBindings.clear();
549
+ for (const ws of clients) ws.close();
550
+ clients.clear();
551
+ clientSessions.clear();
552
+ workSnapshots.clear();
553
+ citizenMessageCounts.clear();
554
+ lastPushHash = "";
555
+ wss.close();
556
+ wss = null;
557
+ console.log("[agentshire] WebSocket server stopped");
558
+ }
559
+
560
+ /**
561
+ * Broadcast an AgentEvent to all connected town frontends.
562
+ * Wraps in `{ type: 'agent_event', event }` for the frontend WS protocol.
563
+ */
564
+ export function broadcastAgentEvent(event: AgentEvent, townSessionId?: string): void {
565
+ if (townSessionId) {
566
+ updateWorkSnapshot(townSessionId, event);
567
+ console.log(
568
+ `${sessionLogPrefix(townSessionId)} WS → agent_event type=${event.type}${"subtype" in event ? `/${String((event as { subtype: string }).subtype)}` : ""}`,
569
+ );
570
+ }
571
+
572
+ if (clients.size === 0) return;
573
+ const payload = JSON.stringify({ type: "agent_event", event });
574
+ for (const ws of clients) {
575
+ if (ws.readyState === WebSocket.OPEN && (!townSessionId || getClientSessionId(ws) === townSessionId)) {
576
+ ws.send(payload);
577
+ }
578
+ }
579
+ }
580
+
581
+
582
+ export function getConnectedClientCount(): number {
583
+ return clients.size;
584
+ }
585
+
586
+ let lastPushHash = "";
587
+
588
+ export function pushNewChatMessages(townSessionId: string): void {
589
+ invalidateSessionCache();
590
+ const messages = loadNewMessages();
591
+ if (messages.length === 0) return;
592
+
593
+ const latest = messages.slice(-10);
594
+ const hash = latest.map(m => `${m.role}:${m.timestamp}:${m.text.slice(0, 50)}`).join("|");
595
+ if (hash === lastPushHash) return;
596
+ lastPushHash = hash;
597
+ console.log(`${sessionLogPrefix(townSessionId)} WS → chat_new_messages agentId=steward count=${latest.length}`);
598
+
599
+ const payload = JSON.stringify({
600
+ type: "chat_new_messages",
601
+ agentId: "steward",
602
+ messages: latest,
603
+ });
604
+ for (const ws of clients) {
605
+ if (ws.readyState === WebSocket.OPEN && getClientSessionId(ws) === townSessionId) {
606
+ ws.send(payload);
607
+ }
608
+ }
609
+ }
610
+
611
+ const citizenMessageCounts = new Map<string, number>();
612
+
613
+ export function pushCitizenMessages(agentId: string, townSessionId: string): void {
614
+ const messages = loadCitizenNewMessages(agentId);
615
+ if (messages.length === 0) return;
616
+ const countKey = `${townSessionId}:${agentId}`;
617
+ const prevCount = citizenMessageCounts.get(countKey) ?? 0;
618
+ if (messages.length === prevCount) return;
619
+
620
+ const nextMessages =
621
+ messages.length > prevCount
622
+ ? messages.slice(prevCount)
623
+ : messages.slice(-5);
624
+ citizenMessageCounts.set(countKey, messages.length);
625
+
626
+ const npcId = findCitizenNpcIdByAgentId(agentId) ?? undefined;
627
+ console.log(
628
+ `${sessionLogPrefix(townSessionId)} WS → chat_new_messages agentId=${agentId} npcId=${npcId ?? "unknown"} count=${nextMessages.length}`,
629
+ );
630
+
631
+ const payload = JSON.stringify({
632
+ type: "chat_new_messages",
633
+ agentId,
634
+ ...(npcId ? { npcId } : {}),
635
+ messages: nextMessages,
636
+ });
637
+ for (const ws of clients) {
638
+ if (ws.readyState === WebSocket.OPEN && getClientSessionId(ws) === townSessionId) {
639
+ ws.send(payload);
640
+ }
641
+ }
642
+ }
643
+
644
+ export function pushSubagentCompletion(childSessionKey: string, townSessionId: string): void {
645
+ try {
646
+ const msg = loadSubagentFinalMessage(childSessionKey);
647
+ if (!msg || !msg.text) return;
648
+
649
+ console.log(`${sessionLogPrefix(townSessionId)} WS → chat_new_messages (subagent completion) text="${msg.text.slice(0, 60)}"`);
650
+
651
+ invalidateSessionCache();
652
+ const allMessages = loadNewMessages();
653
+ allMessages.push(msg);
654
+ allMessages.sort((a, b) => a.timestamp - b.timestamp);
655
+ const latest = allMessages.slice(-10);
656
+ const hash = latest.map(m => `${m.role}:${m.timestamp}:${m.text.slice(0, 50)}`).join("|");
657
+ lastPushHash = hash;
658
+
659
+ const payload = JSON.stringify({
660
+ type: "chat_new_messages",
661
+ agentId: "steward",
662
+ messages: latest,
663
+ });
664
+ for (const ws of clients) {
665
+ if (ws.readyState === WebSocket.OPEN && getClientSessionId(ws) === townSessionId) {
666
+ ws.send(payload);
667
+ }
668
+ }
669
+ } catch (err) {
670
+ console.warn(`[agentshire] pushSubagentCompletion error:`, (err as Error).message);
671
+ }
672
+ }
673
+
674
+ export function clearEventBuffer(townSessionId?: string): void {
675
+ if (townSessionId) {
676
+ workSnapshots.delete(townSessionId);
677
+ return;
678
+ }
679
+ workSnapshots.clear();
680
+ }