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,838 @@
1
+ /**
2
+ * Reads steward session JSONL files and extracts user/assistant text messages.
3
+ * Supports cross-session aggregation: reads from multiple sessions sorted by time.
4
+ */
5
+ import { readFileSync, readdirSync, existsSync, statSync, openSync, closeSync, fstatSync, readSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { stateDir } from "./paths.js";
8
+
9
+ const TOWN_AGENT_ID = "town-steward";
10
+ const MAX_SESSIONS = 100;
11
+ const MAX_ARCHIVED_SESSIONS = 10;
12
+
13
+ export interface ChatHistoryMessage {
14
+ role: "user" | "assistant";
15
+ text: string;
16
+ timestamp: number;
17
+ type?: "text" | "image" | "video" | "audio" | "file";
18
+ imageData?: string;
19
+ mimeType?: string;
20
+ fileUrl?: string;
21
+ fileName?: string;
22
+ fileSize?: number;
23
+ }
24
+
25
+ export interface ChatHistoryCursor {
26
+ sessionIdx: number;
27
+ fileOffset: number;
28
+ }
29
+
30
+ export interface ChatHistoryResult {
31
+ messages: ChatHistoryMessage[];
32
+ hasMore: boolean;
33
+ cursor: string;
34
+ }
35
+
36
+ interface SessionEntry {
37
+ sessionId: string;
38
+ updatedAt: number;
39
+ filePath: string;
40
+ }
41
+
42
+ function sessionsDir(agentId: string = TOWN_AGENT_ID): string {
43
+ return join(stateDir(), "agents", agentId, "sessions");
44
+ }
45
+
46
+ function listArchivedSessions(
47
+ dirPath: string,
48
+ excludeSessionIds: Set<string>,
49
+ maxFiles: number = MAX_ARCHIVED_SESSIONS,
50
+ ): SessionEntry[] {
51
+ try {
52
+ const files = readdirSync(dirPath);
53
+ return files
54
+ .filter(f => f.includes(".jsonl.reset."))
55
+ .map(f => {
56
+ const sessionId = f.split(".jsonl.reset.")[0];
57
+ const resetTs = f.split(".reset.")[1] ?? "";
58
+ return { sessionId, resetTs, path: join(dirPath, f) };
59
+ })
60
+ .filter(f => f.sessionId && !excludeSessionIds.has(f.sessionId))
61
+ .sort((a, b) => b.resetTs.localeCompare(a.resetTs))
62
+ .slice(0, maxFiles)
63
+ .map(f => ({
64
+ sessionId: f.sessionId,
65
+ updatedAt: parseResetTimestamp(f.resetTs),
66
+ filePath: f.path,
67
+ }));
68
+ } catch {
69
+ return [];
70
+ }
71
+ }
72
+
73
+ function parseResetTimestamp(ts: string): number {
74
+ try {
75
+ const normalized = ts.replace(/(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})\.(\d+)Z/, "$1T$2:$3:$4.$5Z");
76
+ const ms = Date.parse(normalized);
77
+ return Number.isFinite(ms) ? ms : 0;
78
+ } catch {
79
+ return 0;
80
+ }
81
+ }
82
+
83
+ function listTownSessions(): SessionEntry[] {
84
+ try {
85
+ const dir = sessionsDir();
86
+ const indexPath = join(dir, "sessions.json");
87
+ if (!existsSync(indexPath)) return [];
88
+ const index = JSON.parse(readFileSync(indexPath, "utf-8"));
89
+
90
+ const sessions: SessionEntry[] = [];
91
+ const indexedIds = new Set<string>();
92
+ for (const [key, value] of Object.entries(index)) {
93
+ if (!key.startsWith("town:") && !key.startsWith("agent:town-steward:town:")) continue;
94
+ const entry = value as any;
95
+ if (!entry?.sessionId) continue;
96
+ const filePath = join(dir, `${entry.sessionId}.jsonl`);
97
+ if (!existsSync(filePath)) continue;
98
+ indexedIds.add(entry.sessionId);
99
+ sessions.push({
100
+ sessionId: entry.sessionId,
101
+ updatedAt: entry.updatedAt ?? 0,
102
+ filePath,
103
+ });
104
+ }
105
+
106
+ sessions.push(...listArchivedSessions(dir, indexedIds));
107
+ sessions.sort((a, b) => b.updatedAt - a.updatedAt);
108
+ return sessions.slice(0, MAX_SESSIONS);
109
+ } catch {
110
+ return [];
111
+ }
112
+ }
113
+
114
+ function stripUserMetadata(text: string): string {
115
+ const marker = '```\n\n';
116
+ const lastPos = text.lastIndexOf(marker);
117
+ if (lastPos >= 0) {
118
+ return text.slice(lastPos + marker.length).trim();
119
+ }
120
+ let cleaned = text.replace(/^Conversation info \(untrusted metadata\):[\s\S]*?\n\n/, "").trim();
121
+ cleaned = cleaned.replace(/^Sender \(untrusted metadata\):[\s\S]*?\n\n/, "").trim();
122
+ return cleaned;
123
+ }
124
+
125
+ function stripReasoningTags(text: string): string {
126
+ const finalMatch = text.match(/<final\b[^>]*>([\s\S]*?)<\/final>/i);
127
+ if (finalMatch) return finalMatch[1].trim();
128
+ return text.replace(/<\/?(?:final|think(?:ing)?|thought|antthinking)\b[^<>]*>/gi, "").trim();
129
+ }
130
+
131
+ function isSystemMessage(text: string): boolean {
132
+ if (text.startsWith("[系统通知]")) return true;
133
+ if (text.startsWith("System:")) return true;
134
+ if (text.startsWith("[Subagent")) return true;
135
+ if (text.trim() === "NO_REPLY") return true;
136
+ return false;
137
+ }
138
+
139
+ function readSubagentFinalMessage(filePath: string): ChatHistoryMessage | null {
140
+ try {
141
+ const content = readFileSync(filePath, "utf-8");
142
+ let last: ChatHistoryMessage | null = null;
143
+
144
+ for (const line of content.split("\n")) {
145
+ const trimmed = line.trim();
146
+ if (!trimmed) continue;
147
+ try {
148
+ const entry = JSON.parse(trimmed);
149
+ if (entry.type !== "message") continue;
150
+ const msg = entry.message;
151
+ if (!msg || msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
152
+ const timestamp = typeof msg.timestamp === "number" ? msg.timestamp : 0;
153
+
154
+ for (const block of msg.content) {
155
+ if (block.type !== "text" || typeof block.text !== "string") continue;
156
+ const text = stripReasoningTags(block.text.trim());
157
+ if (text && !isSystemMessage(text) && text !== "NO_REPLY") {
158
+ last = { role: "assistant", text, timestamp, type: "text" };
159
+ }
160
+ }
161
+ } catch { continue; }
162
+ }
163
+ return last;
164
+ } catch {
165
+ return null;
166
+ }
167
+ }
168
+
169
+ function resolveSubagentMessages(childSessionKeys: string[]): ChatHistoryMessage[] {
170
+ if (childSessionKeys.length === 0) return [];
171
+ const results: ChatHistoryMessage[] = [];
172
+ try {
173
+ const dir = sessionsDir();
174
+ const indexPath = join(dir, "sessions.json");
175
+ if (!existsSync(indexPath)) return [];
176
+ const index = JSON.parse(readFileSync(indexPath, "utf-8"));
177
+
178
+ for (const childKey of childSessionKeys) {
179
+ const entry = (index as Record<string, any>)[childKey];
180
+ if (!entry?.sessionId) continue;
181
+ const childPath = join(dir, `${entry.sessionId}.jsonl`);
182
+ if (!existsSync(childPath)) continue;
183
+ const msg = readSubagentFinalMessage(childPath);
184
+ if (msg) results.push(msg);
185
+ }
186
+ } catch { /* ignore */ }
187
+ return results;
188
+ }
189
+
190
+ function readSessionMessages(filePath: string): ChatHistoryMessage[] {
191
+ try {
192
+ const content = readFileSync(filePath, "utf-8");
193
+ const messages: ChatHistoryMessage[] = [];
194
+ const childSessionKeys: string[] = [];
195
+
196
+ for (const line of content.split("\n")) {
197
+ const trimmed = line.trim();
198
+ if (!trimmed) continue;
199
+ try {
200
+ const entry = JSON.parse(trimmed);
201
+ if (entry.type !== "message") continue;
202
+ const msg = entry.message;
203
+ if (!msg || !Array.isArray(msg.content)) continue;
204
+
205
+ const role = msg.role;
206
+ if (role === "toolResult" && msg.toolName === "sessions_spawn") {
207
+ for (const block of msg.content) {
208
+ if (block.type === "text" && typeof block.text === "string") {
209
+ try {
210
+ const parsed = JSON.parse(block.text);
211
+ if (typeof parsed.childSessionKey === "string") {
212
+ childSessionKeys.push(parsed.childSessionKey);
213
+ }
214
+ } catch { /* not JSON */ }
215
+ }
216
+ }
217
+ }
218
+
219
+ if (role !== "user" && role !== "assistant" && role !== "toolResult") continue;
220
+ const timestamp = typeof msg.timestamp === "number" ? msg.timestamp : 0;
221
+
222
+ for (const block of msg.content) {
223
+ if (block.type === "image" && block.data) {
224
+ messages.push({
225
+ role: "assistant",
226
+ text: "",
227
+ timestamp,
228
+ type: "image",
229
+ imageData: block.data,
230
+ mimeType: block.mimeType ?? "image/png",
231
+ });
232
+ } else if (block.type === "text" && typeof block.text === "string") {
233
+ const raw = block.text.trim();
234
+ if (!raw) continue;
235
+
236
+ const mediaMatch = raw.match(/^MEDIA:(.+)$/);
237
+ if (mediaMatch) {
238
+ const mediaPath = mediaMatch[1].trim();
239
+ const ext = mediaPath.split(".").pop()?.toLowerCase() ?? "";
240
+ const imageExts = ["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp"];
241
+ const videoExts = ["mp4", "webm", "mov", "avi", "mkv"];
242
+ const audioExts = ["mp3", "wav", "ogg", "m4a", "flac", "aac"];
243
+ const fileName = mediaPath.split("/").pop() ?? "file";
244
+ const mediaUrl = `/citizen-workshop/_api/media?path=${encodeURIComponent(mediaPath)}`;
245
+
246
+ if (imageExts.includes(ext)) {
247
+ messages.push({
248
+ role: "assistant", text: "", timestamp,
249
+ type: "image", fileUrl: mediaUrl,
250
+ mimeType: `image/${ext === "jpg" ? "jpeg" : ext}`,
251
+ });
252
+ } else if (videoExts.includes(ext)) {
253
+ messages.push({
254
+ role: "assistant", text: fileName, timestamp,
255
+ type: "video", fileUrl: mediaUrl, fileName,
256
+ mimeType: `video/${ext === "mov" ? "quicktime" : ext}`,
257
+ });
258
+ } else if (audioExts.includes(ext)) {
259
+ messages.push({
260
+ role: "assistant", text: fileName, timestamp,
261
+ type: "audio", fileUrl: mediaUrl, fileName,
262
+ mimeType: `audio/${ext === "m4a" ? "mp4" : ext}`,
263
+ });
264
+ } else {
265
+ messages.push({
266
+ role: "assistant", text: fileName, timestamp,
267
+ type: "file", fileUrl: mediaUrl, fileName,
268
+ });
269
+ }
270
+ continue;
271
+ }
272
+
273
+ if (role === "toolResult") {
274
+ try {
275
+ const parsed = JSON.parse(raw);
276
+ const urls: string[] = parsed.mediaUrls ?? (parsed.mediaUrl ? [parsed.mediaUrl] : []);
277
+ for (const mediaPath of urls) {
278
+ if (!mediaPath || typeof mediaPath !== "string") continue;
279
+ const ext = mediaPath.split(".").pop()?.toLowerCase() ?? "";
280
+ const imageExts = ["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp"];
281
+ const videoExts = ["mp4", "webm", "mov", "avi", "mkv"];
282
+ const audioExts = ["mp3", "wav", "ogg", "m4a", "flac", "aac"];
283
+ const fileName = mediaPath.split("/").pop() ?? "file";
284
+ const fileUrl = `/citizen-workshop/_api/media?path=${encodeURIComponent(mediaPath)}`;
285
+
286
+ if (imageExts.includes(ext)) {
287
+ messages.push({ role: "assistant", text: "", timestamp, type: "image", fileUrl, mimeType: `image/${ext === "jpg" ? "jpeg" : ext}` });
288
+ } else if (videoExts.includes(ext)) {
289
+ messages.push({ role: "assistant", text: fileName, timestamp, type: "video", fileUrl, fileName, mimeType: `video/${ext === "mov" ? "quicktime" : ext}` });
290
+ } else if (audioExts.includes(ext)) {
291
+ messages.push({ role: "assistant", text: fileName, timestamp, type: "audio", fileUrl, fileName, mimeType: `audio/${ext === "m4a" ? "mp4" : ext}` });
292
+ } else if (ext) {
293
+ messages.push({ role: "assistant", text: fileName, timestamp, type: "file", fileUrl, fileName });
294
+ }
295
+ }
296
+ } catch { /* not JSON */ }
297
+ continue;
298
+ }
299
+ if (role !== "user" && role !== "assistant") continue;
300
+
301
+ const mediaAttachMatch = raw.match(/\[media attached: (.+?) \(([^)]+)\)\]/);
302
+ const text = role === "user" ? stripUserMetadata(raw) : stripReasoningTags(raw);
303
+ if (!text && !mediaAttachMatch) continue;
304
+ if (text && isSystemMessage(text)) { if (!mediaAttachMatch) continue; }
305
+
306
+ if (mediaAttachMatch) {
307
+ const mediaPath = mediaAttachMatch[1];
308
+ const mime = mediaAttachMatch[2];
309
+ const ext = mediaPath.split(".").pop()?.toLowerCase() ?? "";
310
+ const imageExts = ["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp"];
311
+ const videoExts = ["mp4", "webm", "mov", "avi", "mkv"];
312
+ const audioExts = ["mp3", "wav", "ogg", "m4a", "flac", "aac"];
313
+ const fileName = mediaPath.split("/").pop() ?? "file";
314
+ const fileUrl = `/citizen-workshop/_api/media?path=${encodeURIComponent(mediaPath)}`;
315
+ let fileSize: number | undefined;
316
+ try { fileSize = statSync(mediaPath).size; } catch {}
317
+
318
+ const remainText = text.replace(/\[media attached: .+? \([^)]+\)\]/, "").trim();
319
+ const caption = remainText.replace(/^To send an image back[\s\S]*$/, "").trim();
320
+ const captionText = (caption && !isSystemMessage(caption)) ? caption : "";
321
+
322
+ if (imageExts.includes(ext)) {
323
+ messages.push({ role: role as "user" | "assistant", text: captionText, timestamp, type: "image", fileUrl, mimeType: mime, fileSize });
324
+ } else if (videoExts.includes(ext)) {
325
+ messages.push({ role: role as "user" | "assistant", text: captionText || fileName, timestamp, type: "video", fileUrl, fileName, mimeType: mime, fileSize });
326
+ } else if (audioExts.includes(ext)) {
327
+ messages.push({ role: role as "user" | "assistant", text: captionText || fileName, timestamp, type: "audio", fileUrl, fileName, mimeType: mime, fileSize });
328
+ } else {
329
+ messages.push({ role: role as "user" | "assistant", text: captionText || fileName, timestamp, type: "file", fileUrl, fileName, fileSize });
330
+ }
331
+ continue;
332
+ }
333
+
334
+ messages.push({ role: role as "user" | "assistant", text, timestamp, type: "text" });
335
+ }
336
+ }
337
+ } catch { continue; }
338
+ }
339
+
340
+ const subMessages = resolveSubagentMessages(childSessionKeys);
341
+ if (subMessages.length > 0) messages.push(...subMessages);
342
+
343
+ return messages;
344
+ } catch {
345
+ return [];
346
+ }
347
+ }
348
+
349
+ export function loadSubagentFinalMessage(childSessionKey: string): ChatHistoryMessage | null {
350
+ try {
351
+ const dir = sessionsDir();
352
+ const indexPath = join(dir, "sessions.json");
353
+ if (!existsSync(indexPath)) return null;
354
+ const index = JSON.parse(readFileSync(indexPath, "utf-8"));
355
+ const entry = (index as Record<string, any>)[childSessionKey];
356
+ if (!entry?.sessionId) return null;
357
+ const filePath = join(dir, `${entry.sessionId}.jsonl`);
358
+ if (!existsSync(filePath)) return null;
359
+ return readSubagentFinalMessage(filePath);
360
+ } catch {
361
+ return null;
362
+ }
363
+ }
364
+
365
+ let cachedSessions: SessionEntry[] | null = null;
366
+ let cachedSessionsTime = 0;
367
+ const CACHE_TTL = 5000;
368
+
369
+ function getSessions(): SessionEntry[] {
370
+ const now = Date.now();
371
+ if (cachedSessions && now - cachedSessionsTime < CACHE_TTL) return cachedSessions;
372
+ cachedSessions = listTownSessions();
373
+ cachedSessionsTime = now;
374
+ return cachedSessions;
375
+ }
376
+
377
+ export function invalidateSessionCache(): void {
378
+ cachedSessions = null;
379
+ }
380
+
381
+ export function loadChatHistory(
382
+ limit: number = 50,
383
+ cursorStr?: string,
384
+ ): ChatHistoryResult {
385
+ const sessions = getSessions();
386
+ if (sessions.length === 0) {
387
+ return { messages: [], hasMore: false, cursor: "" };
388
+ }
389
+
390
+ let startSessionIdx = 0;
391
+ if (cursorStr) {
392
+ try {
393
+ const c: ChatHistoryCursor = JSON.parse(cursorStr);
394
+ startSessionIdx = c.sessionIdx;
395
+ } catch {}
396
+ }
397
+
398
+ const allMessages: ChatHistoryMessage[] = [];
399
+ let lastSessionIdx = startSessionIdx;
400
+
401
+ for (let i = startSessionIdx; i < sessions.length && allMessages.length < limit + 10; i++) {
402
+ const msgs = readSessionMessages(sessions[i].filePath);
403
+ allMessages.push(...msgs);
404
+ lastSessionIdx = i;
405
+ }
406
+
407
+ allMessages.sort((a, b) => a.timestamp - b.timestamp);
408
+
409
+ if (cursorStr) {
410
+ const latest = allMessages.slice(-limit);
411
+ const hasMore = allMessages.length > limit || lastSessionIdx + 1 < sessions.length;
412
+ const cursor: ChatHistoryCursor = { sessionIdx: lastSessionIdx + 1, fileOffset: 0 };
413
+ return { messages: latest, hasMore, cursor: JSON.stringify(cursor) };
414
+ }
415
+
416
+ const latest = allMessages.slice(-limit);
417
+ const hasMore = allMessages.length > limit || sessions.length > 1;
418
+ const cursor: ChatHistoryCursor = { sessionIdx: Math.min(1, sessions.length), fileOffset: 0 };
419
+ return { messages: latest, hasMore, cursor: JSON.stringify(cursor) };
420
+ }
421
+
422
+ export function loadNewMessages(
423
+ currentSessionId?: string,
424
+ ): ChatHistoryMessage[] {
425
+ invalidateSessionCache();
426
+ const sessions = getSessions();
427
+ if (sessions.length === 0) return [];
428
+
429
+ const target = currentSessionId
430
+ ? sessions.find(s => s.sessionId === currentSessionId) ?? sessions[0]
431
+ : sessions[0];
432
+
433
+ return readSessionMessages(target.filePath);
434
+ }
435
+
436
+ export function getCurrentSessionId(): string | null {
437
+ const sessions = getSessions();
438
+ return sessions.length > 0 ? sessions[0].sessionId : null;
439
+ }
440
+
441
+ export function loadCitizenHistory(
442
+ agentId: string,
443
+ limit: number = 50,
444
+ ): ChatHistoryResult {
445
+ const sessDir = sessionsDir(agentId);
446
+
447
+ if (!existsSync(sessDir)) {
448
+ return { messages: [], hasMore: false, cursor: "" };
449
+ }
450
+
451
+ try {
452
+ const allMessages: ChatHistoryMessage[] = [];
453
+ const indexedIds = new Set<string>();
454
+ const prefix = `agent:${agentId}:`;
455
+
456
+ const indexPath = join(sessDir, "sessions.json");
457
+ if (existsSync(indexPath)) {
458
+ const index = JSON.parse(readFileSync(indexPath, "utf-8"));
459
+ for (const [key, value] of Object.entries(index)) {
460
+ if (!key.startsWith(prefix)) continue;
461
+ const entry = value as any;
462
+ if (!entry?.sessionId) continue;
463
+ const filePath = join(sessDir, `${entry.sessionId}.jsonl`);
464
+ if (!existsSync(filePath)) continue;
465
+ indexedIds.add(entry.sessionId);
466
+ allMessages.push(...readSessionMessages(filePath));
467
+ }
468
+ }
469
+
470
+ const archived = listArchivedSessions(sessDir, indexedIds);
471
+ for (const arc of archived) {
472
+ allMessages.push(...readSessionMessages(arc.filePath));
473
+ }
474
+
475
+ allMessages.sort((a, b) => a.timestamp - b.timestamp);
476
+ const latest = allMessages.slice(-limit);
477
+ return { messages: latest, hasMore: allMessages.length > limit, cursor: "" };
478
+ } catch {
479
+ return { messages: [], hasMore: false, cursor: "" };
480
+ }
481
+ }
482
+
483
+ // ────────────────────────────────────────────────────────────────
484
+ // ChatItem-based session parser (Phase 1 — session-first chat)
485
+ // Existing ChatHistoryMessage functions are kept untouched above.
486
+ // ────────────────────────────────────────────────────────────────
487
+
488
+ import type { ChatItem, ChatItemHistoryResult } from "../contracts/chat.js";
489
+ import { resolveAsset, detectMediaType, resolveMimeType } from "./chat-asset-resolver.js";
490
+
491
+ function stripUserMeta(text: string): string {
492
+ const marker = '```\n\n';
493
+ const lastPos = text.lastIndexOf(marker);
494
+ if (lastPos >= 0) return text.slice(lastPos + marker.length).trim();
495
+ let cleaned = text.replace(/^Conversation info \(untrusted metadata\):[\s\S]*?\n\n/, "").trim();
496
+ cleaned = cleaned.replace(/^Sender \(untrusted metadata\):[\s\S]*?\n\n/, "").trim();
497
+ return cleaned;
498
+ }
499
+
500
+ function stripReasoning(text: string): string {
501
+ const finalMatch = text.match(/<final\b[^>]*>([\s\S]*?)<\/final>/i);
502
+ if (finalMatch) return finalMatch[1].trim();
503
+ return text.replace(/<\/?(?:final|think(?:ing)?|thought|antthinking)\b[^<>]*>/gi, "").trim();
504
+ }
505
+
506
+ function isSysText(text: string): boolean {
507
+ if (text.startsWith("[系统通知]")) return true;
508
+ if (text.startsWith("System:")) return true;
509
+ if (text.startsWith("[Subagent")) return true;
510
+ if (text.trim() === "NO_REPLY") return true;
511
+ return false;
512
+ }
513
+
514
+ function extractAttachedMediaPaths(text: string): string[] {
515
+ const results: string[] = [];
516
+ for (const match of text.matchAll(/\[media attached: (.+?) \(([^)]+)\)\]/g)) {
517
+ const mediaPath = match[1]?.trim();
518
+ if (mediaPath) results.push(mediaPath);
519
+ }
520
+ return results;
521
+ }
522
+
523
+ function stripUserAttachmentHints(text: string): string {
524
+ return text
525
+ .replace(/\[media attached: .+? \([^)]+\)\]\n?/g, "")
526
+ .replace(/^To send an image back,.*$/gm, "")
527
+ .trim();
528
+ }
529
+
530
+ /**
531
+ * Shared parser state — pass between calls when processing entries incrementally.
532
+ */
533
+ export interface TranscriptParserState {
534
+ pendingToolCalls: Map<string, { name: string; args: Record<string, unknown> }>;
535
+ }
536
+
537
+ export function createParserState(): TranscriptParserState {
538
+ return { pendingToolCalls: new Map() };
539
+ }
540
+
541
+ /**
542
+ * Parse a single transcript entry into ChatItem[].
543
+ * Shared by both full-file history restore and realtime delta.
544
+ */
545
+ export function parseTranscriptEntry(
546
+ entry: Record<string, unknown>,
547
+ agentId: string,
548
+ state: TranscriptParserState,
549
+ ): ChatItem[] {
550
+ const items: ChatItem[] = [];
551
+
552
+ if (entry.type === "custom_message") {
553
+ const ct = entry.customType as string | undefined;
554
+ if (ct === "openclaw.sessions_yield") {
555
+ items.push({
556
+ id: `status:${(entry as any).id ?? ""}:yielded`,
557
+ agentId,
558
+ timestamp: typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) : 0,
559
+ kind: "status",
560
+ status: "yielded",
561
+ text: String((entry as any).details?.message ?? "Turn yielded."),
562
+ });
563
+ }
564
+ return items;
565
+ }
566
+
567
+ if (entry.type !== "message") return items;
568
+ const msg = (entry as any).message as Record<string, any> | undefined;
569
+ if (!msg || !Array.isArray(msg.content)) return items;
570
+
571
+ const entryId = String((entry as any).id ?? "");
572
+ const role = String(msg.role ?? "");
573
+ const ts = typeof msg.timestamp === "number" ? msg.timestamp : (typeof entry.timestamp === "string" ? Date.parse(entry.timestamp as string) : 0);
574
+ const blocks: any[] = msg.content;
575
+
576
+ if (role === "user") {
577
+ let blockIdx = 0;
578
+ for (const b of blocks) {
579
+ if (b.type === "text" && typeof b.text === "string") {
580
+ const rawText = b.text.trim();
581
+ const mediaPaths = extractAttachedMediaPaths(rawText);
582
+ for (const mediaPath of mediaPaths) {
583
+ const asset = resolveAsset(mediaPath);
584
+ items.push({
585
+ id: `msg:${entryId}:media:${blockIdx}:${mediaPath}`,
586
+ agentId, timestamp: ts, kind: "media", role: "user",
587
+ ...asset,
588
+ });
589
+ }
590
+ const text = stripUserAttachmentHints(stripUserMeta(rawText));
591
+ if (text) {
592
+ items.push({ id: `msg:${entryId}:text:${blockIdx}`, agentId, timestamp: ts, kind: "text", role: "user", text, source: "user_input" });
593
+ }
594
+ } else if (b.type === "image" && b.data) {
595
+ items.push({
596
+ id: `msg:${entryId}:media:${blockIdx}`,
597
+ agentId, timestamp: ts, kind: "media", role: "user",
598
+ mediaType: "image", fileUrl: "", fileName: "image",
599
+ mimeType: b.mimeType ?? "image/png", imageData: b.data,
600
+ });
601
+ }
602
+ blockIdx++;
603
+ }
604
+ return items;
605
+ }
606
+
607
+ if (role === "assistant") {
608
+ let blockIdx = 0;
609
+ for (const b of blocks) {
610
+ if (b.type === "toolCall") {
611
+ const toolId = String(b.id ?? `tool-${ts}-${blockIdx}`);
612
+ const toolName = String(b.name ?? "unknown");
613
+ const args = (b.arguments ?? {}) as Record<string, unknown>;
614
+ state.pendingToolCalls.set(toolId, { name: toolName, args });
615
+ items.push({
616
+ id: `tool:${toolId}:start`,
617
+ agentId, timestamp: ts, kind: "tool", phase: "start",
618
+ toolUseId: toolId, toolName, input: args,
619
+ });
620
+ } else if (b.type === "text" && typeof b.text === "string") {
621
+ const text = stripReasoning(b.text.trim());
622
+ if (text && !isSysText(text)) {
623
+ items.push({ id: `msg:${entryId}:text:${blockIdx}`, agentId, timestamp: ts, kind: "text", role: "assistant", text, source: "llm" });
624
+ }
625
+ } else if (b.type === "image" && b.data) {
626
+ items.push({
627
+ id: `msg:${entryId}:media:${blockIdx}`,
628
+ agentId, timestamp: ts, kind: "media", role: "assistant",
629
+ mediaType: "image", fileUrl: "", fileName: "image",
630
+ mimeType: b.mimeType ?? "image/png", imageData: b.data,
631
+ });
632
+ }
633
+ blockIdx++;
634
+ }
635
+ return items;
636
+ }
637
+
638
+ if (role === "toolResult") {
639
+ const toolCallId = String(msg.toolCallId ?? "");
640
+ const toolName = String(msg.toolName ?? "unknown");
641
+ const pending = state.pendingToolCalls.get(toolCallId);
642
+ if (pending) state.pendingToolCalls.delete(toolCallId);
643
+
644
+ let resultText = "";
645
+ for (const b of blocks) {
646
+ if (b.type !== "text" || typeof b.text !== "string") continue;
647
+ resultText = b.text.trim();
648
+ }
649
+
650
+ items.push({
651
+ id: `tool:${toolCallId || entryId}:end`,
652
+ agentId, timestamp: ts, kind: "tool", phase: "end",
653
+ toolUseId: toolCallId, toolName,
654
+ outputText: resultText.slice(0, 500),
655
+ isError: msg.isError === true,
656
+ });
657
+
658
+ if (toolName === "message" && resultText) {
659
+ try {
660
+ const parsed = JSON.parse(resultText);
661
+ const urls: string[] = parsed.mediaUrls ?? (parsed.mediaUrl ? [parsed.mediaUrl] : []);
662
+ for (const mediaPath of urls) {
663
+ if (!mediaPath || typeof mediaPath !== "string") continue;
664
+ const asset = resolveAsset(mediaPath);
665
+ items.push({
666
+ id: `msg:${entryId}:media:${mediaPath}`,
667
+ agentId, timestamp: ts, kind: "media", role: "assistant",
668
+ ...asset, caption: parsed.caption ?? parsed.summary ?? "",
669
+ });
670
+ }
671
+ } catch {}
672
+ } else if (resultText) {
673
+ try {
674
+ const parsed = JSON.parse(resultText);
675
+ const urls: string[] = parsed.mediaUrls ?? (parsed.mediaUrl ? [parsed.mediaUrl] : []);
676
+ for (const mediaPath of urls) {
677
+ if (!mediaPath || typeof mediaPath !== "string") continue;
678
+ const asset = resolveAsset(mediaPath);
679
+ items.push({
680
+ id: `msg:${entryId}:media:${mediaPath}`,
681
+ agentId, timestamp: ts, kind: "media", role: "assistant",
682
+ ...asset,
683
+ });
684
+ }
685
+ } catch {
686
+ const mediaMatch = resultText.match(/^MEDIA:(.+)$/);
687
+ if (mediaMatch) {
688
+ const asset = resolveAsset(mediaMatch[1].trim());
689
+ items.push({
690
+ id: `msg:${entryId}:media:${mediaMatch[1].trim()}`,
691
+ agentId, timestamp: ts, kind: "media", role: "assistant",
692
+ ...asset,
693
+ });
694
+ }
695
+ }
696
+ }
697
+
698
+ const attachMatch = resultText.match(/\[media attached: (.+?) \(([^)]+)\)\]/);
699
+ if (attachMatch) {
700
+ const mediaPath = attachMatch[1];
701
+ const asset = resolveAsset(mediaPath);
702
+ items.push({
703
+ id: `msg:${entryId}:media:${mediaPath}`,
704
+ agentId, timestamp: ts, kind: "media", role: "assistant",
705
+ ...asset,
706
+ });
707
+ }
708
+ }
709
+
710
+ return items;
711
+ }
712
+
713
+ /**
714
+ * Parse a full session transcript file into ChatItem[].
715
+ * Uses parseTranscriptEntry() internally.
716
+ */
717
+ export function readSessionItems(filePath: string, agentId: string): ChatItem[] {
718
+ try {
719
+ const content = readFileSync(filePath, "utf-8");
720
+ const items: ChatItem[] = [];
721
+ const state = createParserState();
722
+
723
+ for (const line of content.split("\n")) {
724
+ const trimmed = line.trim();
725
+ if (!trimmed) continue;
726
+ try {
727
+ const entry = JSON.parse(trimmed);
728
+ items.push(...parseTranscriptEntry(entry, agentId, state));
729
+ } catch { continue; }
730
+ }
731
+
732
+ return items;
733
+ } catch {
734
+ return [];
735
+ }
736
+ }
737
+
738
+ export function loadChatItemHistory(
739
+ limit: number = 50,
740
+ cursorStr?: string,
741
+ ): ChatItemHistoryResult {
742
+ invalidateSessionCache();
743
+ const sessions = getSessions();
744
+ if (sessions.length === 0) return { items: [], hasMore: false, cursor: "" };
745
+
746
+ let startIdx = 0;
747
+ if (cursorStr) {
748
+ try { startIdx = JSON.parse(cursorStr).sessionIdx ?? 0; } catch {}
749
+ }
750
+
751
+ const allItems: ChatItem[] = [];
752
+ let lastIdx = startIdx;
753
+ const countVisible = () => allItems.filter(it => it.kind === "text" || it.kind === "media").length;
754
+
755
+ for (let i = startIdx; i < sessions.length && countVisible() < limit + 10; i++) {
756
+ allItems.push(...readSessionItems(sessions[i].filePath, "steward"));
757
+ lastIdx = i;
758
+ }
759
+
760
+ allItems.sort((a, b) => a.timestamp - b.timestamp);
761
+ const visibleItems = allItems.filter(it => it.kind === "text" || it.kind === "media");
762
+ const latest = visibleItems.slice(-limit);
763
+ const hasMore = visibleItems.length > limit || lastIdx + 1 < sessions.length;
764
+ const cursor = JSON.stringify({ sessionIdx: cursorStr ? lastIdx + 1 : Math.min(1, sessions.length) });
765
+ return { items: latest, hasMore, cursor };
766
+ }
767
+
768
+ export function loadCitizenItemHistory(
769
+ agentId: string,
770
+ limit: number = 50,
771
+ ): ChatItemHistoryResult {
772
+ const sessDir = sessionsDir(agentId);
773
+
774
+ if (!existsSync(sessDir)) return { items: [], hasMore: false, cursor: "" };
775
+
776
+ try {
777
+ const allItems: ChatItem[] = [];
778
+ const indexedIds = new Set<string>();
779
+ const prefix = `agent:${agentId}:`;
780
+
781
+ const indexPath = join(sessDir, "sessions.json");
782
+ if (existsSync(indexPath)) {
783
+ const index = JSON.parse(readFileSync(indexPath, "utf-8"));
784
+ for (const [key, value] of Object.entries(index)) {
785
+ if (!key.startsWith(prefix)) continue;
786
+ const entry = value as any;
787
+ if (!entry?.sessionId) continue;
788
+ const fp = join(sessDir, `${entry.sessionId}.jsonl`);
789
+ if (!existsSync(fp)) continue;
790
+ indexedIds.add(entry.sessionId);
791
+ allItems.push(...readSessionItems(fp, agentId));
792
+ }
793
+ }
794
+
795
+ const archived = listArchivedSessions(sessDir, indexedIds);
796
+ for (const arc of archived) {
797
+ allItems.push(...readSessionItems(arc.filePath, agentId));
798
+ }
799
+
800
+ allItems.sort((a, b) => a.timestamp - b.timestamp);
801
+ const visibleItems = allItems.filter(it => it.kind === "text" || it.kind === "media");
802
+ const latest = visibleItems.slice(-limit);
803
+ return { items: latest, hasMore: visibleItems.length > limit, cursor: "" };
804
+ } catch {
805
+ return { items: [], hasMore: false, cursor: "" };
806
+ }
807
+ }
808
+
809
+ // ────────────────────────────────────────────────────────────────
810
+ // Legacy functions below — kept for backward compatibility
811
+ // ────────────────────────────────────────────────────────────────
812
+
813
+ export function loadCitizenNewMessages(agentId: string): ChatHistoryMessage[] {
814
+ const sessDir = sessionsDir(agentId);
815
+ const indexPath = join(sessDir, "sessions.json");
816
+
817
+ if (!existsSync(indexPath)) return [];
818
+
819
+ try {
820
+ const index = JSON.parse(readFileSync(indexPath, "utf-8"));
821
+ const prefix = `agent:${agentId}:`;
822
+ let latest: any = null;
823
+ for (const [key, value] of Object.entries(index)) {
824
+ if (!key.startsWith(prefix)) continue;
825
+ const entry = value as any;
826
+ if (!entry?.sessionId) continue;
827
+ if (!latest || (entry.updatedAt ?? 0) > (latest.updatedAt ?? 0)) {
828
+ latest = entry;
829
+ }
830
+ }
831
+ if (!latest) return [];
832
+ const filePath = join(sessDir, `${latest.sessionId}.jsonl`);
833
+ if (!existsSync(filePath)) return [];
834
+ return readSessionMessages(filePath);
835
+ } catch {
836
+ return [];
837
+ }
838
+ }