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,1076 @@
1
+ // @desc Central orchestrator: translates AgentEvents into a phased town narrative (idle → summoning → assigning → working → publishing → returning)
2
+ // @desc Central orchestrator: translates AgentEvents into a phased town narrative (idle → summoning → assigning → working → publishing → returning)
3
+ import { EventTranslator } from './EventTranslator.js'
4
+ import type { GameEvent, NPCPhase } from '../../town-frontend/src/data/GameProtocol.js'
5
+ import type { AgentEvent } from '../contracts/events.js'
6
+ import { StateTracker } from './StateTracker.js'
7
+ import { getCharacterKeyForNpc, pickUnusedCharacterKey } from '../../town-frontend/src/data/CharacterRoster.js'
8
+ import { NpcEventQueue } from './NpcEventQueue.js'
9
+ import { RouteManager, CITIZEN_SPAWN_ORIGIN, PLAZA_CENTER, STEWARD_FACE_POS } from './RouteManager.js'
10
+ import { toolToVfxEvents, toolEmoji, extractFilePath, inferDeliverableCardType, CARD_TYPES } from './ToolVfxMapper.js'
11
+ import { ActivityStream } from './ActivityStream.js'
12
+ import { CitizenManager } from './CitizenManager.js'
13
+
14
+ type Phase = 'idle' | 'summoning' | 'assigning' | 'going_to_office' | 'working' | 'publishing' | 'returning'
15
+
16
+ function isToolSuccess(name: string, output: string): boolean {
17
+ if (['read', 'read_file', 'grep', 'glob'].includes(name)) return true
18
+ if (['write', 'write_file', 'edit', 'edit_file'].includes(name)) return /^Successfully/.test(output)
19
+ const errorPatterns = [
20
+ /^Error:/i, /^node:/, /EADDRINUSE/, /ENOENT/, /EACCES/,
21
+ /throw\s+er;/, /command not found/i, /permission denied/i,
22
+ /^fatal:/i, /^SyntaxError:/, /^TypeError:/, /^ReferenceError:/,
23
+ ]
24
+ return !errorPatterns.some(p => p.test(output))
25
+ }
26
+
27
+ interface AgentInfo {
28
+ agentId: string
29
+ npcId: string
30
+ displayName: string
31
+ task: string
32
+ status: 'pending' | 'working' | 'completed' | 'failed'
33
+ avatarId?: string
34
+ }
35
+
36
+ const OFFICE_DOOR_SPAWN = { x: 15, z: 24 }
37
+
38
+ const WORKSTATION_IDS = ['B', 'C', 'F', 'G', 'D', 'H', 'E', 'A']
39
+
40
+ const SUMMON_COLLECT_WINDOW = 3000
41
+
42
+
43
+
44
+ /** Facade that orchestrates the town's response to agent events. Delegates to RouteManager (pathfinding), ActivityStream (logs), CitizenManager (citizen lifecycle), and ToolVfxMapper (VFX). Manages the phase state machine and sub-agent lifecycle. */
45
+ export class DirectorBridge {
46
+ private tracker = new StateTracker()
47
+ private translator = new EventTranslator(this.tracker)
48
+ private phase: Phase = 'idle'
49
+ private agents = new Map<string, AgentInfo>()
50
+ private agentOrder: string[] = []
51
+ private summonTimer: ReturnType<typeof setTimeout> | null = null
52
+ private emitFn: ((events: GameEvent[]) => void) | null = null
53
+ private emitBuffer: GameEvent[][] = []
54
+ private stewardName = 'steward'
55
+ private activeToolCount = 0
56
+ private personaName: string | null = null
57
+ private stewardPersonaConfirmed = true
58
+ private townConfig: any = null
59
+ private personaChangedFn: ((name: string) => void) | null = null
60
+ private npcQueues = new Map<string, NpcEventQueue>()
61
+ private progressRingId: number | null = null
62
+ private lastToolInput: Record<string, unknown> = {}
63
+ private pendingProjectName = ''
64
+ private pendingProjectType = ''
65
+ private bubbleDebugEnabled = this.readBubbleDebugFlag()
66
+ private systemInitReceived = false
67
+ private workflowSummonEmitted = false
68
+ private npcCharacterAssignments = new Map<string, string>([
69
+ ['user', getCharacterKeyForNpc('user')],
70
+ ['steward', getCharacterKeyForNpc('steward')],
71
+ ])
72
+ private tempWorkerNpcIds = new Set<string>()
73
+ private lastStewardText = ''
74
+ private pendingWorkSnapshot: {
75
+ phase: string
76
+ stewardPersona?: string
77
+ agents: Array<{ id: string; displayName: string; task: string; status: string; activityLog?: Array<{ name: string; input: Record<string, unknown>; output?: string; success?: boolean }> }>
78
+ } | null = null
79
+ private routes = new RouteManager((events) => this.emit(events))
80
+ private activity = new ActivityStream((events) => this.emit(events))
81
+ private citizens = new CitizenManager({
82
+ emit: (events) => this.emit(events),
83
+ routes: this.routes,
84
+ getConfig: () => this.townConfig,
85
+ getCharacterAssignments: () => this.npcCharacterAssignments,
86
+ isStewardConfirmed: () => this.stewardPersonaConfirmed,
87
+ getPersonaName: () => this.personaName,
88
+ getStewardName: () => this.stewardName,
89
+ onPersonaChanged: (name) => this.personaChangedFn?.(name),
90
+ delayMs: (ms) => this.delayMs(ms),
91
+ scheduleDelayedEmit: (delay, events) => this.scheduleDelayedEmit(delay, events),
92
+ getLastToolInput: () => this.lastToolInput,
93
+ })
94
+
95
+ private readBubbleDebugFlag(): boolean {
96
+ try {
97
+ const g = globalThis as unknown as { location?: { search?: string }; localStorage?: { getItem?: (key: string) => string | null } }
98
+ const search = typeof g.location?.search === 'string'
99
+ ? new URLSearchParams(g.location.search)
100
+ : null
101
+ if (search?.get('bubbleDebug') === '1') return true
102
+ const local = g.localStorage?.getItem?.('agentshire_bubble_debug')
103
+ return local === '1' || local === 'true'
104
+ } catch {
105
+ return false
106
+ }
107
+ }
108
+
109
+ private logBubbleIngress(stage: string, text: string): void {
110
+ if (!this.bubbleDebugEnabled) return
111
+ console.log(`[BubbleDebug][Bridge][${stage}] ${JSON.stringify(text)}`)
112
+ }
113
+
114
+ onEmit(fn: (events: GameEvent[]) => void): void {
115
+ this.emitFn = fn
116
+ if (this.emitBuffer.length > 0) {
117
+ const buffered = this.emitBuffer
118
+ this.emitBuffer = []
119
+ for (const events of buffered) {
120
+ fn(events)
121
+ }
122
+ }
123
+ }
124
+
125
+ onPersonaChanged(fn: (name: string) => void): void {
126
+ this.personaChangedFn = fn
127
+ }
128
+
129
+ setPersonaName(name: string): void {
130
+ this.personaName = name
131
+ this.emit([{ type: 'steward_rename', npcId: 'steward', newName: name }])
132
+ }
133
+
134
+ setTownConfig(config: any): boolean {
135
+ this.townConfig = config
136
+ if (config?.steward?.avatarId) {
137
+ this.npcCharacterAssignments.set('steward', config.steward.avatarId)
138
+ }
139
+ if (config?.user?.avatarId) {
140
+ this.npcCharacterAssignments.set('user', config.user.avatarId)
141
+ }
142
+ if (config?.citizens?.length > 0) {
143
+ for (const c of config.citizens) {
144
+ if (c.id && c.avatarId) {
145
+ this.npcCharacterAssignments.set(c.id, c.avatarId)
146
+ }
147
+ }
148
+ this.emit([{ type: 'town_config_ready', config }])
149
+ }
150
+ return !!this.pendingWorkSnapshot
151
+ }
152
+
153
+ executePendingRestore(): void {
154
+ if (!this.pendingWorkSnapshot) return
155
+ const snap = this.pendingWorkSnapshot
156
+ this.pendingWorkSnapshot = null
157
+ this.doRestoreWorkState(snap)
158
+ }
159
+
160
+ onStewardRenamed(name: string): void {
161
+ this.personaName = name
162
+ this.personaChangedFn?.(name)
163
+ }
164
+
165
+ onUserMessage(text: string): void {
166
+ this.emit([{ type: 'dialog_message', npcId: 'user', text, isStreaming: false }])
167
+ }
168
+
169
+ private getQueue(npcId: string): NpcEventQueue {
170
+ let q = this.npcQueues.get(npcId)
171
+ if (!q) {
172
+ q = new NpcEventQueue((events) => this.emit(events))
173
+ this.npcQueues.set(npcId, q)
174
+ }
175
+ return q
176
+ }
177
+
178
+ /** Handle a GameAction from the frontend (user message, abort, door click, move ack) */
179
+ processWorldAction(action: any): { type: string; message?: string } | null {
180
+ switch (action.type) {
181
+ case 'user_message':
182
+ return { type: 'chat', message: action.text }
183
+ case 'abort_requested':
184
+ return { type: 'abort' }
185
+ case 'steward_renamed':
186
+ this.onStewardRenamed(action.name)
187
+ return null
188
+ case 'town_setup_complete':
189
+ return null
190
+ case 'return_to_town':
191
+ if (this.phase === 'working') {
192
+ this.emit([{ type: 'scene_switch', target: 'town' }])
193
+ }
194
+ return null
195
+ case 'building_door_clicked':
196
+ if (this.phase === 'working' && action.buildingId === 'office') {
197
+ this.emit([{ type: 'scene_switch', target: 'office' }])
198
+ }
199
+ return null
200
+ case 'npc_move_completed':
201
+ this.routes.resolveMoveRequest(String(action.requestId ?? ''), String(action.npcId ?? ''), action.status === 'arrived' ? 'arrived' : 'interrupted')
202
+ return null
203
+ case 'workstation_released': {
204
+ const npcId = String(action.npcId ?? '')
205
+ const stationId = String(action.stationId ?? this.tracker.getStationForNpc(npcId) ?? '')
206
+ if (stationId) {
207
+ this.tracker.releaseStation(stationId)
208
+ }
209
+ if (npcId) {
210
+ this.tracker.removeMappingByNpcId(npcId)
211
+ }
212
+ return null
213
+ }
214
+ case 'workflow_phase_complete': {
215
+ const completedPhase = String(action.phase ?? '')
216
+ console.log(`[DirectorBridge] workflow_phase_complete: ${completedPhase}, current phase: ${this.phase}`)
217
+ if (completedPhase === 'summoning' && this.phase === 'summoning') {
218
+ this.phase = 'assigning'
219
+ this.emit([{ type: 'mode_change', mode: 'work', workSubState: 'assigning' }])
220
+ const agents = this.agentOrder.map(id => this.agents.get(id)!).filter(Boolean)
221
+ this.emit([{
222
+ type: 'workflow_assign',
223
+ agents: agents.map(a => ({ npcId: a.npcId, displayName: a.displayName, task: a.task })),
224
+ } as GameEvent])
225
+ } else if (completedPhase === 'assigning' && this.phase === 'assigning') {
226
+ this.phase = 'going_to_office'
227
+ const agents = this.agentOrder.map(id => this.agents.get(id)!).filter(Boolean)
228
+ this.emit([
229
+ { type: 'mode_change', mode: 'work', workSubState: 'going_to_office' },
230
+ { type: 'workflow_go_office', agents: agents.map(a => ({ npcId: a.npcId })) } as GameEvent,
231
+ ])
232
+ } else if (completedPhase === 'going_to_office' && this.phase === 'going_to_office') {
233
+ this.phase = 'working'
234
+ const doneCount = [...this.agents.values()].filter(a => a.status === 'completed' || a.status === 'failed').length
235
+ const totalCount = this.agents.size
236
+ this.emit([
237
+ { type: 'mode_change', mode: 'work', workSubState: 'working' },
238
+ { type: 'progress', current: doneCount, total: totalCount, label: `${doneCount}/${totalCount} 完成` },
239
+ ])
240
+ } else if (completedPhase === 'publishing' && this.phase === 'publishing') {
241
+ this.phase = 'returning'
242
+ this.emit([{ type: 'mode_change', mode: 'work', workSubState: 'returning' }])
243
+ const retAgents = this.agentOrder.map(id => this.agents.get(id)!).filter(Boolean)
244
+ this.emit([{
245
+ type: 'workflow_return',
246
+ agents: retAgents.map(a => ({ npcId: a.npcId })),
247
+ wasInOffice: true,
248
+ } as GameEvent])
249
+ } else if (completedPhase === 'returning' && this.phase === 'returning') {
250
+ this.phase = 'idle'
251
+ this.activeToolCount = 0
252
+ this.emit([
253
+ { type: 'mode_change', mode: 'life' },
254
+ { type: 'npc_phase', npcId: this.stewardName, phase: 'idle' },
255
+ ])
256
+ this.agents.clear()
257
+ this.agentOrder = []
258
+ this.tracker.clear()
259
+ this.pendingProjectName = ''
260
+ this.pendingProjectType = ''
261
+ if (this.citizens.pendingStewardRenameTimer) {
262
+ clearTimeout(this.citizens.pendingStewardRenameTimer)
263
+ this.citizens.pendingStewardRenameTimer = null
264
+ }
265
+ this.routes.destinationClaimCount.clear()
266
+ this.routes.npcDestinationClaim.clear()
267
+ this.routes.npcLastDestination.clear()
268
+ for (const q of this.npcQueues.values()) q.flush()
269
+ this.npcQueues.clear()
270
+ this.workflowSummonEmitted = false
271
+ }
272
+ return null
273
+ }
274
+ default:
275
+ return null
276
+ }
277
+ }
278
+
279
+ getPersonaSyncEvents(): GameEvent[] {
280
+ if (this.personaName) {
281
+ return [{ type: 'steward_rename', npcId: 'steward', newName: this.personaName }]
282
+ }
283
+ return []
284
+ }
285
+
286
+ restoreWorkState(snapshot: {
287
+ phase: string
288
+ stewardPersona?: string
289
+ agents: Array<{ id: string; displayName: string; task: string; status: string; activityLog?: Array<{ name: string; input: Record<string, unknown>; output?: string; success?: boolean }> }>
290
+ }): void {
291
+ if (!snapshot.agents?.length) return
292
+
293
+ if (!this.townConfig) {
294
+ console.log('[DirectorBridge] restoreWorkState: townConfig not ready, deferring')
295
+ this.pendingWorkSnapshot = snapshot
296
+ return
297
+ }
298
+
299
+ this.doRestoreWorkState(snapshot)
300
+ }
301
+
302
+ private doRestoreWorkState(snapshot: {
303
+ phase: string
304
+ stewardPersona?: string
305
+ agents: Array<{ id: string; displayName: string; task: string; status: string; activityLog?: Array<{ name: string; input: Record<string, unknown>; output?: string; success?: boolean }> }>
306
+ }): void {
307
+ console.log('[DirectorBridge] doRestoreWorkState: restoring', snapshot.agents.length, 'agents')
308
+
309
+ this.agents.clear()
310
+ this.agentOrder = []
311
+ this.tracker.clear()
312
+
313
+ if (snapshot.stewardPersona) {
314
+ this.personaName = snapshot.stewardPersona
315
+ this.stewardPersonaConfirmed = true
316
+ }
317
+
318
+ const agentInfos: Array<{ npcId: string; displayName: string; task: string; status: string; avatarId: string }> = []
319
+
320
+ for (const a of snapshot.agents) {
321
+ const rawName = a.displayName ?? a.id.replace(/^agent_/, '')
322
+ const displayName = rawName.replace(/^agent_/, '').replace(/_/g, ' ').replace(/\b\w/g, (c: string) => c.toUpperCase())
323
+ let npcId = this.citizens.findCitizenNpcId(displayName) ?? this.citizens.findCitizenNpcId(rawName) ?? a.id.replace(/^agent_/, '')
324
+ if (npcId === 'steward' || npcId === 'user') {
325
+ npcId = `temp_${a.id.replace(/^agent_/, '').slice(0, 8)}_${Date.now().toString(36)}`
326
+ }
327
+
328
+ const isTempWorker = !this.citizens.findCitizenNpcId(displayName) && !this.citizens.findCitizenNpcId(rawName)
329
+ if (isTempWorker) this.tempWorkerNpcIds.add(npcId)
330
+
331
+ this.tracker.registerMapping(a.id, npcId)
332
+ const resolvedStatus = (a.status === 'completed' || a.status === 'failed') ? a.status : 'working'
333
+ const info: AgentInfo = {
334
+ agentId: a.id,
335
+ npcId,
336
+ displayName,
337
+ task: a.task,
338
+ status: resolvedStatus as AgentInfo['status'],
339
+ }
340
+ this.agents.set(a.id, info)
341
+ this.agentOrder.push(a.id)
342
+
343
+ let avatarId: string
344
+ if (isTempWorker) {
345
+ avatarId = pickUnusedCharacterKey(this.npcCharacterAssignments)
346
+ } else {
347
+ const configured = this.townConfig?.citizens?.find((c: any) => c.id === npcId)
348
+ avatarId = configured?.avatarId ?? getCharacterKeyForNpc(npcId)
349
+ }
350
+ this.npcCharacterAssignments.set(npcId, avatarId)
351
+ agentInfos.push({ npcId, displayName, task: a.task, status: resolvedStatus, avatarId })
352
+ }
353
+
354
+ this.phase = 'working'
355
+
356
+ this.emit([{
357
+ type: 'restore_work_state',
358
+ agents: agentInfos,
359
+ }])
360
+
361
+ for (const a of snapshot.agents) {
362
+ const log = a.activityLog
363
+ if (!log || log.length === 0) continue
364
+ const info = this.agents.get(a.id)
365
+ if (!info) continue
366
+
367
+ const entries: Array<{ kind: string; icon?: string; message?: string; status?: string }> = []
368
+ for (const entry of log) {
369
+ const icon = this.activity.toolActivityIcon(entry.name)
370
+ const message = this.activity.toolActivityMsg(entry.name, entry.input)
371
+ const status = entry.success === true ? 'success' : entry.success === false ? 'error' : 'none'
372
+ entries.push({ kind: 'activity', icon, message, status })
373
+ }
374
+ if (entries.length > 0) {
375
+ this.emit([{ type: 'npc_activity_restore', npcId: info.npcId, entries }])
376
+ }
377
+ }
378
+ }
379
+
380
+ /** Main entry point: dispatch an AgentEvent to the appropriate handler based on type */
381
+ processAgentEvent(event: AgentEvent): void {
382
+ if (event.type !== 'text_delta' && event.type !== 'thinking_delta' && event.type !== 'tool_input_delta') {
383
+ console.log('[DirectorBridge] event:', event.type, 'phase:', this.phase, 'name' in event ? (event as { name?: string }).name ?? '' : '')
384
+ }
385
+ if (event.type !== 'text' && event.type !== 'text_delta') {
386
+ this.lastStewardText = ''
387
+ }
388
+ switch (event.type) {
389
+ case 'sub_agent':
390
+ this.handleSubAgent(event)
391
+ return
392
+ case 'text_delta':
393
+ this.lastStewardText = ''
394
+ this.handleStewardText(event)
395
+ return
396
+ case 'text': {
397
+ const content = (event as { content?: string }).content ?? ''
398
+ if (content && content === this.lastStewardText) return
399
+ this.lastStewardText = content
400
+ this.handleStewardText(event)
401
+ return
402
+ }
403
+ case 'thinking_delta':
404
+ this.activity.appendThinkingDelta(this.stewardName, event.delta ?? '')
405
+ if (this.phase === 'idle' || this.phase === 'working') {
406
+ console.log('[DirectorBridge] → steward thinking')
407
+ this.getQueue(this.stewardName).enqueuePhase([
408
+ { type: 'npc_phase', npcId: this.stewardName, phase: 'thinking' },
409
+ ])
410
+ }
411
+ return
412
+ case 'tool_input_delta':
413
+ if (this.phase === 'idle' || this.phase === 'working') {
414
+ if (this.activeToolCount === 0) {
415
+ this.getQueue(this.stewardName).enqueuePhase([
416
+ { type: 'npc_phase', npcId: this.stewardName, phase: 'thinking' },
417
+ ])
418
+ }
419
+ }
420
+ return
421
+ case 'tool_use':
422
+ console.log('[DirectorBridge] tool_use:', event.name, 'phase:', this.phase, 'toolCount:', this.activeToolCount)
423
+ this.activity.flushThinking(this.stewardName)
424
+ this.lastToolInput = event.input ?? {}
425
+ if (this.activity.isTodoWrite(event.name ?? '')) {
426
+ this.activity.emitTodoActivity(this.stewardName, event.input ?? {})
427
+ } else {
428
+ this.activity.emitActivity(this.stewardName, this.activity.toolActivityIcon(event.name ?? ''), this.activity.toolActivityMsg(event.name ?? '', event.input))
429
+ }
430
+ if (this.phase === 'idle' || this.phase === 'working') {
431
+ this.activeToolCount++
432
+ const { events: toolEvents, phase: toolPhase } = toolToVfxEvents(event.name ?? '', this.stewardName, event.input)
433
+ this.getQueue(this.stewardName).enqueuePhase([
434
+ { type: 'npc_phase', npcId: this.stewardName, phase: toolPhase as NPCPhase },
435
+ ...toolEvents,
436
+ ])
437
+ }
438
+ return
439
+ case 'tool_result':
440
+ console.log('[DirectorBridge] tool_result:', event.name, 'phase:', this.phase)
441
+ this.activity.emitActivityStatus(this.stewardName, isToolSuccess(event.name ?? '', event.output ?? ''))
442
+ void this.citizens.detectPersonaSwitch(event)
443
+ this.citizens.detectCitizenCreated(event)
444
+ if (event.name === 'register_project') {
445
+ if (this.lastToolInput.name) this.pendingProjectName = String(this.lastToolInput.name)
446
+ if (this.lastToolInput.type) this.pendingProjectType = String(this.lastToolInput.type)
447
+ }
448
+ if (event.name === 'project_complete') {
449
+ if (this.phase === 'idle' || this.phase === 'working') {
450
+ this.activeToolCount = Math.max(0, this.activeToolCount - 1)
451
+ }
452
+ this.handleProjectComplete(event)
453
+ return
454
+ }
455
+ if (event.name === 'deliver_output') {
456
+ if (this.phase === 'idle' || this.phase === 'working') {
457
+ this.activeToolCount = Math.max(0, this.activeToolCount - 1)
458
+ }
459
+ this.handleDeliverOutput()
460
+ return
461
+ }
462
+
463
+ if (this.phase === 'idle' || this.phase === 'working') {
464
+ this.activeToolCount = Math.max(0, this.activeToolCount - 1)
465
+ if (this.activeToolCount === 0) {
466
+ this.getQueue(this.stewardName).enqueuePhase([
467
+ { type: 'npc_phase', npcId: this.stewardName, phase: 'thinking' },
468
+ { type: 'npc_emoji', npcId: this.stewardName, emoji: null },
469
+ ])
470
+ }
471
+ }
472
+ return
473
+ case 'turn_end':
474
+ this.activity.flushThinking(this.stewardName)
475
+ for (const q of this.npcQueues.values()) q.flush()
476
+ this.citizens.flushPendingCitizens()
477
+ if (this.phase === 'summoning') {
478
+ if (this.summonTimer) { clearTimeout(this.summonTimer); this.summonTimer = null }
479
+ this.emitWorkflowSummon()
480
+ return
481
+ }
482
+ if (this.phase === 'idle' || this.phase === 'working') {
483
+ this.activeToolCount = 0
484
+ this.emit([{ type: 'npc_phase', npcId: this.stewardName, phase: 'idle' }])
485
+ }
486
+ return
487
+ case 'llm_call':
488
+ if (event.subtype === 'start') {
489
+ if (this.phase === 'idle' || this.phase === 'working') {
490
+ this.getQueue(this.stewardName).enqueuePhase([
491
+ { type: 'npc_phase', npcId: this.stewardName, phase: 'thinking' },
492
+ ])
493
+ }
494
+ }
495
+ return
496
+ case 'error':
497
+ this.emit([{ type: 'npc_phase', npcId: this.stewardName, phase: 'error' }])
498
+ return
499
+ case 'bus_message':
500
+ if (event.from && event.to) {
501
+ this.emit([
502
+ { type: 'npc_look_at', npcId: event.from, targetNpcId: event.to },
503
+ { type: 'fx', effect: 'connectionBeam', params: { fromNpcId: event.from, toNpcId: event.to } },
504
+ ])
505
+ if (event.summary) {
506
+ this.emit([{ type: 'dialog_message', npcId: event.from, text: event.summary, isStreaming: false }])
507
+ }
508
+ }
509
+ return
510
+ case 'hook_activity':
511
+ if (this.phase !== 'working') {
512
+ this.emit([{ type: 'fx', effect: 'hookFlash', params: { npcId: this.stewardName } }])
513
+ }
514
+ return
515
+ case 'media_output': {
516
+ if (event.role !== 'assistant') return
517
+ if (event.source === 'tts') return
518
+ const kindMap: Record<string, string> = { image: 'image', video: 'video', audio: 'audio', file: 'file' }
519
+ const mediaCardType = kindMap[event.kind] ?? 'file'
520
+ console.log('[DirectorBridge] media_output → deliverable_card:', mediaCardType, event.path)
521
+ this.emit([
522
+ {
523
+ type: 'deliverable_card',
524
+ cardType: mediaCardType,
525
+ name: event.meta?.filename || (typeof event.meta?.originalPath === 'string' ? event.meta.originalPath.split('/').pop() : undefined) || event.path?.split('/').pop(),
526
+ filePath: event.path,
527
+ mimeType: event.mimeType,
528
+ thumbnailData: event.kind === 'image' ? event.data : undefined,
529
+ data: event.data,
530
+ } as GameEvent,
531
+ ])
532
+ return
533
+ }
534
+ case 'system': {
535
+ const sysEvent = event as Extract<AgentEvent, { type: 'system' }>
536
+ if (sysEvent.subtype === 'init') {
537
+ if (this.systemInitReceived) return
538
+ this.systemInitReceived = true
539
+ }
540
+ if (sysEvent.subtype === 'done') {
541
+ this.systemInitReceived = false
542
+ }
543
+ this.emit(this.translator.translate(event))
544
+ return
545
+ }
546
+ default:
547
+ this.emit(this.translator.translate(event))
548
+ return
549
+ }
550
+ }
551
+
552
+ processCitizenEvent(npcId: string, event: AgentEvent): void {
553
+ const q = this.getQueue(npcId)
554
+ switch (event.type) {
555
+ case 'thinking_delta':
556
+ this.activity.appendThinkingDelta(npcId, event.delta ?? '')
557
+ q.enqueuePhase([
558
+ { type: 'npc_phase', npcId, phase: 'thinking' as NPCPhase },
559
+ ])
560
+ return
561
+ case 'tool_use':
562
+ this.activity.flushThinking(npcId)
563
+ if (this.activity.isTodoWrite(event.name ?? '')) {
564
+ this.activity.emitTodoActivity(npcId, event.input ?? {})
565
+ } else {
566
+ this.activity.emitActivity(npcId, this.activity.toolActivityIcon(event.name ?? ''), this.activity.toolActivityMsg(event.name ?? '', event.input))
567
+ }
568
+ q.enqueuePhase([
569
+ { type: 'npc_phase', npcId, phase: 'working' as NPCPhase },
570
+ ])
571
+ return
572
+ case 'tool_result':
573
+ this.activity.emitActivityStatus(npcId, isToolSuccess(event.name ?? '', event.output ?? ''))
574
+ q.enqueuePhase([
575
+ { type: 'npc_phase', npcId, phase: 'thinking' as NPCPhase },
576
+ ])
577
+ return
578
+ case 'text_delta': {
579
+ const text = event.delta ?? ''
580
+ if (!text) return
581
+ q.enqueueDialog([
582
+ { type: 'dialog_message', npcId, text, isStreaming: true },
583
+ { type: 'npc_look_at', npcId, targetNpcId: 'user' },
584
+ ], text.length)
585
+ return
586
+ }
587
+ case 'text': {
588
+ const text = (event as { content?: string }).content ?? ''
589
+ if (!text) return
590
+ if (text) {
591
+ this.activity.emitActivity(npcId, 'message-circle', text, true)
592
+ }
593
+ q.enqueueDialog([
594
+ { type: 'dialog_message', npcId, text, isStreaming: false },
595
+ { type: 'npc_look_at', npcId, targetNpcId: 'user' },
596
+ ], text.length)
597
+ return
598
+ }
599
+ case 'media_output': {
600
+ if (event.role !== 'assistant') return
601
+ if (event.source === 'tts') return
602
+ const kindMap: Record<string, string> = { image: 'image', video: 'video', audio: 'audio', file: 'file' }
603
+ const mediaCardType = kindMap[event.kind] ?? 'file'
604
+ this.emit([{
605
+ type: 'deliverable_card',
606
+ cardType: mediaCardType,
607
+ name: event.meta?.filename || (typeof event.meta?.originalPath === 'string' ? event.meta.originalPath.split('/').pop() : undefined) || event.path?.split('/').pop(),
608
+ filePath: event.path,
609
+ mimeType: event.mimeType,
610
+ thumbnailData: event.kind === 'image' ? event.data : undefined,
611
+ } as GameEvent])
612
+ return
613
+ }
614
+ case 'turn_end':
615
+ this.activity.flushThinking(npcId)
616
+ q.flush()
617
+ this.emit([{ type: 'npc_phase', npcId, phase: 'idle' as NPCPhase }])
618
+ return
619
+ case 'error':
620
+ this.emit([{ type: 'npc_phase', npcId, phase: 'error' as NPCPhase }])
621
+ return
622
+ }
623
+ }
624
+
625
+ private handleStewardText(event: Extract<AgentEvent, { type: 'text_delta' }> | Extract<AgentEvent, { type: 'text' }>): void {
626
+ const q = this.getQueue(this.stewardName)
627
+ if (event.type === 'text_delta') {
628
+ const text = event.delta ?? ''
629
+ this.logBubbleIngress(`steward:text_delta:${this.stewardName}`, text)
630
+ q.enqueueDialog(
631
+ [
632
+ { type: 'dialog_message', npcId: this.stewardName, text, isStreaming: true },
633
+ { type: 'npc_look_at', npcId: this.stewardName, targetNpcId: 'user' },
634
+ ],
635
+ text.length,
636
+ )
637
+ } else if (event.type === 'text') {
638
+ const text = event.content ?? ''
639
+ this.logBubbleIngress(`steward:text:${this.stewardName}`, text)
640
+ if (text) {
641
+ this.activity.emitActivity(this.stewardName, 'message-circle', text, true)
642
+ }
643
+ q.enqueueDialog(
644
+ [
645
+ { type: 'dialog_message', npcId: this.stewardName, text, isStreaming: false },
646
+ { type: 'npc_look_at', npcId: this.stewardName, targetNpcId: 'user' },
647
+ ],
648
+ text.length,
649
+ )
650
+ }
651
+ }
652
+
653
+ /** Handle sub_agent started/progress/done events: spawn NPCs, relay tool/text events, track completion */
654
+ private handleSubAgent(event: Extract<AgentEvent, { type: 'sub_agent' }>): void {
655
+ const { subtype, agentId } = event
656
+
657
+ if (subtype === 'started') {
658
+ const rawName = event.displayName ?? agentId.replace(/^agent_/, '')
659
+ let displayName = rawName.replace(/^agent_/, '').replace(/_/g, ' ').replace(/\b\w/g, (c: string) => c.toUpperCase())
660
+ const fallbackNpcId = agentId.replace(/^agent_/, '')
661
+ const task = event.task ?? ''
662
+ const extra = event as Extract<AgentEvent, { type: 'sub_agent'; subtype: 'started' }> & { avatarId?: string; metadata?: { avatarId?: string } }
663
+ const avatarId = extra.avatarId ?? extra.metadata?.avatarId ?? undefined
664
+
665
+ let citizenNpcId = this.citizens.findCitizenNpcId(displayName) ?? this.citizens.findCitizenNpcId(rawName)
666
+
667
+ if (!citizenNpcId) {
668
+ citizenNpcId = this.citizens.fuzzyMatchCitizen(displayName) ?? this.citizens.fuzzyMatchCitizen(rawName)
669
+ }
670
+
671
+ if (!citizenNpcId && this.citizens.looksLikeIdFragment(displayName)) {
672
+ displayName = `临时工 ${this.agents.size + 1}`
673
+ }
674
+
675
+ const PROTECTED_IDS = new Set(['steward', 'user'])
676
+ let npcId = citizenNpcId ?? fallbackNpcId
677
+ if (PROTECTED_IDS.has(npcId)) {
678
+ npcId = `temp_${agentId.replace(/^agent_/, '').slice(0, 8)}_${Date.now().toString(36)}`
679
+ }
680
+ const isTempWorker = !citizenNpcId
681
+
682
+ if (isTempWorker) {
683
+ this.tempWorkerNpcIds.add(npcId)
684
+ if (!displayName || this.citizens.looksLikeIdFragment(rawName)) {
685
+ displayName = `临时工 ${this.tempWorkerNpcIds.size}`
686
+ }
687
+ }
688
+
689
+ this.tracker.registerMapping(agentId, npcId)
690
+ const info: AgentInfo = { agentId, npcId, displayName, task, status: 'pending', avatarId }
691
+ this.agents.set(agentId, info)
692
+ this.agentOrder.push(agentId)
693
+
694
+ if (this.phase === 'idle') {
695
+ this.phase = 'summoning'
696
+ this.emit([
697
+ { type: 'npc_phase', npcId: this.stewardName, phase: 'idle' },
698
+ { type: 'npc_emoji', npcId: this.stewardName, emoji: null },
699
+ { type: 'mode_change', mode: 'work', workSubState: 'summoning' },
700
+ { type: 'dialog_message', npcId: this.stewardName, text: '好,我来召唤团队!', isStreaming: false },
701
+ ])
702
+ const RIPPLE_COUNT = 5
703
+ const RIPPLE_INTERVAL = 350
704
+ for (let i = 0; i < RIPPLE_COUNT; i++) {
705
+ this.scheduleDelayedEmit(800 + i * RIPPLE_INTERVAL, [
706
+ { type: 'fx', effect: 'summon_ripple', params: { npcId: this.stewardName } },
707
+ ])
708
+ }
709
+ }
710
+
711
+ this.emitArrival(
712
+ info,
713
+ this.phase === 'working' ? OFFICE_DOOR_SPAWN : undefined,
714
+ )
715
+
716
+ if (this.phase === 'working') {
717
+ this.assignLateArrival(info)
718
+ const doneCount = [...this.agents.values()].filter(a => a.status === 'completed' || a.status === 'failed').length
719
+ const totalCount = this.agents.size
720
+ this.emit([
721
+ { type: 'progress', current: doneCount, total: totalCount, label: `${doneCount}/${totalCount} 完成` },
722
+ ])
723
+ return
724
+ }
725
+
726
+ if (this.phase === 'going_to_office' || this.phase === 'assigning') {
727
+ return
728
+ }
729
+
730
+ if (this.workflowSummonEmitted) return
731
+
732
+ if (this.summonTimer) clearTimeout(this.summonTimer)
733
+ this.summonTimer = setTimeout(() => {
734
+ this.summonTimer = null
735
+ if (this.phase === 'summoning') {
736
+ this.emitWorkflowSummon()
737
+ }
738
+ }, SUMMON_COLLECT_WINDOW)
739
+ return
740
+ }
741
+
742
+ if (subtype === 'progress') {
743
+ const info = this.agents.get(agentId)
744
+ if (!info) return
745
+ const npcId = info.npcId
746
+ const inner = event.event
747
+ if (!inner) return
748
+ const q = this.getQueue(npcId)
749
+ const inWorkPhase = this.phase === 'working' || this.phase === 'going_to_office'
750
+ const canDriveNpcWorkingState = this.phase === 'working'
751
+
752
+ switch (inner.type) {
753
+ case 'thinking_delta':
754
+ if (inWorkPhase) {
755
+ this.activity.appendThinkingDelta(npcId, inner.delta ?? '')
756
+ q.enqueuePhase([
757
+ { type: 'npc_phase', npcId, phase: 'thinking' },
758
+ { type: 'npc_emoji', npcId, emoji: '💭' },
759
+ ])
760
+ }
761
+ break
762
+ case 'tool_use': {
763
+ this.activity.flushThinking(npcId)
764
+ const isThinking = inner.name === '__thinking__' || inner.name === '__thinking_placeholder__'
765
+ if (this.activity.isTodoWrite(inner.name)) {
766
+ this.activity.emitTodoActivity(npcId, inner.input ?? {})
767
+ } else {
768
+ this.activity.emitActivity(npcId, this.activity.toolActivityIcon(inner.name), this.activity.toolActivityMsg(inner.name, inner.input ?? {}), isThinking)
769
+ }
770
+ if (!isThinking && canDriveNpcWorkingState) {
771
+ const toolEmojiStr = toolEmoji(inner.name)
772
+ q.enqueuePhase([
773
+ { type: 'npc_phase', npcId, phase: 'working' },
774
+ { type: 'npc_emoji', npcId, emoji: toolEmojiStr },
775
+ ])
776
+ }
777
+ break
778
+ }
779
+ case 'tool_result': {
780
+ const isThinkingResult = inner.name === '__thinking__'
781
+ if (!isThinkingResult) {
782
+ this.activity.emitActivityStatus(npcId, isToolSuccess(inner.name ?? '', inner.output ?? ''))
783
+ }
784
+ if (inWorkPhase) {
785
+ q.enqueuePhase([{ type: 'npc_emoji', npcId, emoji: null }])
786
+ }
787
+ break
788
+ }
789
+ case 'text_delta': {
790
+ const text = inner.delta ?? ''
791
+ this.logBubbleIngress(`subagent:text_delta:${npcId}`, text)
792
+ if (inWorkPhase) {
793
+ q.enqueueDialog(
794
+ [{ type: 'dialog_message', npcId, text, isStreaming: true }],
795
+ text.length,
796
+ )
797
+ }
798
+ break
799
+ }
800
+ case 'text': {
801
+ const text = inner.content ?? ''
802
+ this.logBubbleIngress(`subagent:text:${npcId}`, text)
803
+ if (text) {
804
+ this.activity.emitActivity(npcId, 'message-circle', text, true)
805
+ }
806
+ if (inWorkPhase) {
807
+ q.enqueueDialog(
808
+ [{ type: 'dialog_message', npcId, text, isStreaming: false }],
809
+ text.length,
810
+ )
811
+ }
812
+ break
813
+ }
814
+ case 'turn_end':
815
+ this.activity.flushThinking(npcId)
816
+ q.flush()
817
+ this.emit([
818
+ { type: 'npc_phase', npcId, phase: 'idle' },
819
+ { type: 'npc_emoji', npcId, emoji: null },
820
+ ])
821
+ break
822
+ case 'error': {
823
+ this.activity.flushThinking(npcId)
824
+ this.activity.emitActivity(npcId, 'alert-circle', '出错')
825
+ q.flush()
826
+ const errStation = this.tracker.getAllNpcStates().find(s => s.npcId === npcId)?.stationId
827
+ this.emit([
828
+ { type: 'npc_phase', npcId, phase: 'error' },
829
+ { type: 'npc_emoji', npcId, emoji: '❌' },
830
+ { type: 'npc_emote', npcId, emote: 'frustrated' },
831
+ ...(errStation ? [{ type: 'workstation_screen', stationId: errStation, state: { mode: 'error' } } as GameEvent] : []),
832
+ ])
833
+ break
834
+ }
835
+ }
836
+ return
837
+ }
838
+
839
+ if (subtype === 'done') {
840
+ const info = this.agents.get(agentId)
841
+ if (!info) return
842
+ const npcId = info.npcId
843
+ const isError = event.status === 'failed'
844
+
845
+ info.status = isError ? 'failed' : 'completed'
846
+ const station = this.tracker.getAllNpcStates().find(s => s.npcId === npcId)?.stationId
847
+ const isTempWorker = this.tempWorkerNpcIds.has(npcId)
848
+
849
+ this.emit([{
850
+ type: 'npc_work_done' as const,
851
+ npcId,
852
+ status: (isError ? 'failed' : 'completed') as 'completed' | 'failed',
853
+ stationId: station,
854
+ isTempWorker,
855
+ } as GameEvent])
856
+
857
+ if (isTempWorker) {
858
+ this.tempWorkerNpcIds.delete(npcId)
859
+ }
860
+
861
+ const doneCount = [...this.agents.values()].filter(a => a.status === 'completed' || a.status === 'failed').length
862
+ const totalCount = this.agents.size
863
+ this.emit([
864
+ { type: 'progress', current: doneCount, total: totalCount, label: `${doneCount}/${totalCount} 完成` },
865
+ ])
866
+
867
+ if (this.allAgentsDone()) {
868
+ console.log('[DirectorBridge] all agents done — waiting for steward to call project_complete')
869
+ }
870
+ return
871
+ }
872
+ }
873
+
874
+ private emitArrival(info: AgentInfo, spawn?: { x: number; z: number }): void {
875
+ let safeAvatarId: string
876
+ if (typeof info.avatarId === 'string' && info.avatarId.trim()) {
877
+ safeAvatarId = info.avatarId.trim()
878
+ } else if (this.tempWorkerNpcIds.has(info.npcId)) {
879
+ safeAvatarId = pickUnusedCharacterKey(this.npcCharacterAssignments)
880
+ } else {
881
+ const configured = this.townConfig?.citizens?.find((c: any) => c.id === info.npcId)
882
+ safeAvatarId = configured?.avatarId ?? getCharacterKeyForNpc(info.npcId)
883
+ }
884
+ this.npcCharacterAssignments.set(info.npcId, safeAvatarId)
885
+
886
+ const spawnVec = spawn ? { x: spawn.x, y: 0, z: spawn.z } : undefined
887
+ this.emit([
888
+ { type: 'npc_spawn', npcId: info.npcId, name: info.displayName, role: 'programming', category: 'citizen', task: info.task, avatarId: safeAvatarId, spawn: spawnVec, arrivalFanfare: true },
889
+ ])
890
+ }
891
+
892
+ private emitWorkflowSummon(): void {
893
+ if (this.workflowSummonEmitted) return
894
+ this.workflowSummonEmitted = true
895
+ const agents = this.agentOrder.map(id => this.agents.get(id)!).filter(Boolean)
896
+ this.emit([{
897
+ type: 'workflow_summon',
898
+ agents: agents.map(a => ({ npcId: a.npcId, displayName: a.displayName, task: a.task })),
899
+ } as GameEvent])
900
+ }
901
+
902
+ private assignLateArrival(info: AgentInfo): void {
903
+ info.status = 'working'
904
+ this.emit([
905
+ { type: 'workstation_assign', npcId: info.npcId, stationId: '' },
906
+ ])
907
+ }
908
+
909
+ private allAgentsDone(): boolean {
910
+ if (this.agents.size === 0) return false
911
+ for (const info of this.agents.values()) {
912
+ if (info.status !== 'completed' && info.status !== 'failed') return false
913
+ }
914
+ return true
915
+ }
916
+
917
+ private handleProjectComplete(event: Extract<AgentEvent, { type: 'tool_result' }>): void {
918
+ const output = String(event?.output ?? '')
919
+ if (/^error:/i.test(output.trim())) {
920
+ console.warn('[DirectorBridge] project_complete rejected by tool:', output)
921
+ return
922
+ }
923
+
924
+ const input = this.lastToolInput ?? {}
925
+ const deliverableType = String(input.type ?? 'operation')
926
+ const summary = String(input.summary ?? '')
927
+ const url = input.url ? String(input.url) : undefined
928
+ const name = input.name ? String(input.name) : this.pendingProjectName || ''
929
+ const files = Array.isArray(input.files)
930
+ ? input.files.map((file: unknown) => String(file ?? '').trim()).filter(Boolean)
931
+ : []
932
+ let cardType = CARD_TYPES[deliverableType] ?? null
933
+ if (!cardType && this.pendingProjectType) {
934
+ cardType = CARD_TYPES[this.pendingProjectType] ?? null
935
+ }
936
+ const enriched = Array.isArray(input._enrichedFiles) ? input._enrichedFiles as Array<{ path: string; fileName: string; mediaType: string; mimeType: string; data?: string; thumbnailData?: string; httpUrl?: string }> : []
937
+ const deliverableCards: GameEvent[] = []
938
+ if (cardType && files.length > 0) {
939
+ const entryFile = files[0]
940
+ const match = enriched.find((e: any) => e.path === entryFile)
941
+ const entryUrl = match?.httpUrl || url || entryFile
942
+ deliverableCards.push({
943
+ type: 'deliverable_card',
944
+ cardType: cardType as 'game' | 'app' | 'website',
945
+ name: name || undefined,
946
+ url: entryUrl,
947
+ } as GameEvent)
948
+ } else if (cardType && url) {
949
+ deliverableCards.push({
950
+ type: 'deliverable_card',
951
+ cardType: cardType as 'game' | 'app' | 'website',
952
+ name: name || undefined,
953
+ url,
954
+ } as GameEvent)
955
+ } else if (deliverableType === 'files' || deliverableType === 'media') {
956
+ for (const filePath of files) {
957
+ const match = enriched.find((e: any) => e.path === filePath)
958
+ if (match && (match.data || match.httpUrl)) {
959
+ deliverableCards.push({
960
+ type: 'deliverable_card',
961
+ cardType: match.mediaType as any,
962
+ name: match.fileName,
963
+ url: match.httpUrl,
964
+ filePath,
965
+ mimeType: match.mimeType,
966
+ data: match.data,
967
+ thumbnailData: match.thumbnailData,
968
+ httpUrl: match.httpUrl,
969
+ } as GameEvent)
970
+ } else {
971
+ deliverableCards.push({
972
+ type: 'deliverable_card',
973
+ cardType: inferDeliverableCardType(filePath),
974
+ name: filePath.split('/').pop() ?? filePath,
975
+ filePath,
976
+ } as GameEvent)
977
+ }
978
+ }
979
+ }
980
+
981
+ console.log(
982
+ '[DirectorBridge] project_complete:',
983
+ deliverableType,
984
+ 'pendingType:',
985
+ this.pendingProjectType,
986
+ 'name:',
987
+ name,
988
+ 'cards:',
989
+ deliverableCards.length,
990
+ 'phase:',
991
+ this.phase,
992
+ )
993
+ if (this.phase === 'publishing' || this.phase === 'returning') {
994
+ if (deliverableCards.length > 0) {
995
+ this.emit(deliverableCards)
996
+ }
997
+ return
998
+ }
999
+
1000
+ const wasInOffice = this.phase === 'working'
1001
+ this.phase = 'publishing'
1002
+ if (wasInOffice) {
1003
+ this.emit([{ type: 'mode_change', mode: 'work', workSubState: 'publishing' }])
1004
+ }
1005
+ const agents = this.agentOrder.map(id => this.agents.get(id)!).filter(Boolean)
1006
+ this.emit([{
1007
+ type: 'workflow_publish',
1008
+ summary: summary || '任务完成了!',
1009
+ deliverableCards: deliverableCards as unknown[],
1010
+ agents: agents.map(a => ({ npcId: a.npcId, displayName: a.displayName, status: a.status })),
1011
+ } as GameEvent])
1012
+ }
1013
+
1014
+ private handleDeliverOutput(): void {
1015
+ const input = this.lastToolInput ?? {}
1016
+ const deliverableType = String(input.type ?? 'operation')
1017
+ const url = input.url ? String(input.url) : undefined
1018
+ const name = input.name ? String(input.name) : ''
1019
+ const files = Array.isArray(input.files)
1020
+ ? input.files.map((file: unknown) => String(file ?? '').trim()).filter(Boolean)
1021
+ : []
1022
+ let cardType = CARD_TYPES[deliverableType] ?? null
1023
+ if (!cardType && this.pendingProjectType) {
1024
+ cardType = CARD_TYPES[this.pendingProjectType] ?? null
1025
+ }
1026
+
1027
+ const cards: GameEvent[] = []
1028
+ if (cardType && url) {
1029
+ cards.push({ type: 'deliverable_card', cardType: cardType as any, name: name || undefined, url } as GameEvent)
1030
+ } else if (deliverableType === 'files' || deliverableType === 'media') {
1031
+ for (const filePath of files) {
1032
+ cards.push({ type: 'deliverable_card', cardType: inferDeliverableCardType(filePath), name: filePath.split('/').pop() ?? filePath, filePath } as GameEvent)
1033
+ }
1034
+ }
1035
+
1036
+ console.log('[DirectorBridge] deliver_output: cards:', cards.length)
1037
+ if (cards.length > 0) this.emit(cards)
1038
+ }
1039
+
1040
+ private emit(events: GameEvent[]): void {
1041
+ for (const event of events) {
1042
+ if (event.type === 'npc_spawn') {
1043
+ const npcId = event.npcId
1044
+ if (npcId) {
1045
+ this.npcCharacterAssignments.set(
1046
+ npcId,
1047
+ getCharacterKeyForNpc(npcId, typeof event.avatarId === 'string' ? event.avatarId : undefined),
1048
+ )
1049
+ }
1050
+ } else if (event.type === 'steward_rename' && typeof event.characterKey === 'string') {
1051
+ this.npcCharacterAssignments.set('steward', event.characterKey)
1052
+ } else if (event.type === 'npc_despawn') {
1053
+ this.npcCharacterAssignments.delete(event.npcId)
1054
+ }
1055
+ }
1056
+ if (events.length > 0) {
1057
+ if (this.emitFn) {
1058
+ this.emitFn(events)
1059
+ } else {
1060
+ this.emitBuffer.push(events)
1061
+ }
1062
+ }
1063
+ }
1064
+
1065
+ private scheduleDelayedEmit(delayMs: number, events: GameEvent[]): void {
1066
+ setTimeout(() => this.emit(events), delayMs)
1067
+ }
1068
+
1069
+ private delayMs(ms: number): Promise<void> {
1070
+ return new Promise(r => setTimeout(r, ms))
1071
+ }
1072
+
1073
+ getActivityReplayEvents(): any[] {
1074
+ return this.activity.getActivityReplayEvents()
1075
+ }
1076
+ }