prts-terrarchive 0.2.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 (287) hide show
  1. package/GAME_ASSETS.md +26 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +274 -0
  4. package/README.md +423 -0
  5. package/THIRD_PARTY_NOTICES.md +61 -0
  6. package/bin/bake-rhine-details.mjs +34 -0
  7. package/bin/install.js +273 -0
  8. package/bin/pack-map-assets.mjs +110 -0
  9. package/bin/preview-rhine.mjs +71 -0
  10. package/bin/rhine-detail-baker.ts +84 -0
  11. package/contracts/cloud_tools_v1.schema.json +243 -0
  12. package/contracts/corpus_i18n_v1.schema.json +179 -0
  13. package/contracts/corpus_tools_v1.schema.json +1801 -0
  14. package/cordis.patch.yml +32 -0
  15. package/docs/endfield-official-i18n.md +103 -0
  16. package/lib/client.js +3912 -0
  17. package/lib/endfield-map/map.js.br +0 -0
  18. package/lib/endfield-map/map.js.gz +0 -0
  19. package/lib/endfield-map/resources/map-00b0d0744a1b4404.json.br +0 -0
  20. package/lib/endfield-map/resources/map-00b0d0744a1b4404.json.gz +0 -0
  21. package/lib/endfield-map/resources/map-01616f534aaaeca1.png +0 -0
  22. package/lib/endfield-map/resources/map-1562e0dff4be3d23.png +0 -0
  23. package/lib/endfield-map/resources/map-160fe040f080f6a3.png +0 -0
  24. package/lib/endfield-map/resources/map-17b9d8be25dc2b39.json.br +0 -0
  25. package/lib/endfield-map/resources/map-17b9d8be25dc2b39.json.gz +0 -0
  26. package/lib/endfield-map/resources/map-1c4245c4e8b80c43.json.br +0 -0
  27. package/lib/endfield-map/resources/map-1c4245c4e8b80c43.json.gz +0 -0
  28. package/lib/endfield-map/resources/map-1d30baaaef3e29cc.png +0 -0
  29. package/lib/endfield-map/resources/map-21a61386af79cd12.json.br +0 -0
  30. package/lib/endfield-map/resources/map-21a61386af79cd12.json.gz +0 -0
  31. package/lib/endfield-map/resources/map-24b920b8dab49b44.json.br +0 -0
  32. package/lib/endfield-map/resources/map-24b920b8dab49b44.json.gz +0 -0
  33. package/lib/endfield-map/resources/map-26caf8d71247abb7.png +0 -0
  34. package/lib/endfield-map/resources/map-2729942c89545e73.json.br +0 -0
  35. package/lib/endfield-map/resources/map-2729942c89545e73.json.gz +0 -0
  36. package/lib/endfield-map/resources/map-29063a9802f67014.json.br +0 -0
  37. package/lib/endfield-map/resources/map-29063a9802f67014.json.gz +0 -0
  38. package/lib/endfield-map/resources/map-2f35d09a3ab60a63.json.br +0 -0
  39. package/lib/endfield-map/resources/map-2f35d09a3ab60a63.json.gz +0 -0
  40. package/lib/endfield-map/resources/map-2f98a873da81425e.json.br +0 -0
  41. package/lib/endfield-map/resources/map-2f98a873da81425e.json.gz +0 -0
  42. package/lib/endfield-map/resources/map-3ab978ae44190f80.json.br +0 -0
  43. package/lib/endfield-map/resources/map-3ab978ae44190f80.json.gz +0 -0
  44. package/lib/endfield-map/resources/map-3b62c80d14208902.json.br +0 -0
  45. package/lib/endfield-map/resources/map-3b62c80d14208902.json.gz +0 -0
  46. package/lib/endfield-map/resources/map-3d486e887ae121ac.png +0 -0
  47. package/lib/endfield-map/resources/map-418dc4865608fce5.png +0 -0
  48. package/lib/endfield-map/resources/map-4366b84607335f76.json.br +0 -0
  49. package/lib/endfield-map/resources/map-4366b84607335f76.json.gz +0 -0
  50. package/lib/endfield-map/resources/map-4371259d3ab7aac7.png +0 -0
  51. package/lib/endfield-map/resources/map-48dcc0fd55e13f7f.png +0 -0
  52. package/lib/endfield-map/resources/map-4aabda0e41197570.json.br +0 -0
  53. package/lib/endfield-map/resources/map-4aabda0e41197570.json.gz +0 -0
  54. package/lib/endfield-map/resources/map-505f5687b287043c.json.br +0 -0
  55. package/lib/endfield-map/resources/map-505f5687b287043c.json.gz +0 -0
  56. package/lib/endfield-map/resources/map-5177e9d1d46f47e2.png +0 -0
  57. package/lib/endfield-map/resources/map-51baeb59b7f8c28d.png +0 -0
  58. package/lib/endfield-map/resources/map-549561b46e621874.png +0 -0
  59. package/lib/endfield-map/resources/map-56c6a0bef799c8dc.json.br +0 -0
  60. package/lib/endfield-map/resources/map-56c6a0bef799c8dc.json.gz +0 -0
  61. package/lib/endfield-map/resources/map-5a6963909423b674.png +0 -0
  62. package/lib/endfield-map/resources/map-5a9c533c0552d7b7.png +0 -0
  63. package/lib/endfield-map/resources/map-5d20ae9ffa9a02b0.png +0 -0
  64. package/lib/endfield-map/resources/map-626c8847393f60bf.png +0 -0
  65. package/lib/endfield-map/resources/map-63d43cbec3bed3b8.png +0 -0
  66. package/lib/endfield-map/resources/map-661b6ec56e722574.png +0 -0
  67. package/lib/endfield-map/resources/map-6ddd384eca190828.png +0 -0
  68. package/lib/endfield-map/resources/map-6fc1aa9144f8156c.json.br +0 -0
  69. package/lib/endfield-map/resources/map-6fc1aa9144f8156c.json.gz +0 -0
  70. package/lib/endfield-map/resources/map-738ebaa002c33cfc.png +0 -0
  71. package/lib/endfield-map/resources/map-758c13be6085ec07.json.br +0 -0
  72. package/lib/endfield-map/resources/map-758c13be6085ec07.json.gz +0 -0
  73. package/lib/endfield-map/resources/map-78929f26d6844509.png +0 -0
  74. package/lib/endfield-map/resources/map-7f09c142950a0f74.png +0 -0
  75. package/lib/endfield-map/resources/map-8385e11f39e1a51c.json.br +0 -0
  76. package/lib/endfield-map/resources/map-8385e11f39e1a51c.json.gz +0 -0
  77. package/lib/endfield-map/resources/map-881343926a568f27.json.br +0 -0
  78. package/lib/endfield-map/resources/map-881343926a568f27.json.gz +0 -0
  79. package/lib/endfield-map/resources/map-8d1ade3f01d952b0.png +0 -0
  80. package/lib/endfield-map/resources/map-8fef3495bb7ed60f.png +0 -0
  81. package/lib/endfield-map/resources/map-907a7ecf8a0ce352.png +0 -0
  82. package/lib/endfield-map/resources/map-9113d5951c4db903.json.br +0 -0
  83. package/lib/endfield-map/resources/map-9113d5951c4db903.json.gz +0 -0
  84. package/lib/endfield-map/resources/map-94ad6634436224e3.json.br +0 -0
  85. package/lib/endfield-map/resources/map-94ad6634436224e3.json.gz +0 -0
  86. package/lib/endfield-map/resources/map-99722e61da5f3e53.png +0 -0
  87. package/lib/endfield-map/resources/map-9be9596cc059abe3.png +0 -0
  88. package/lib/endfield-map/resources/map-a4db9a4763e47fbf.png +0 -0
  89. package/lib/endfield-map/resources/map-a4e8ef07159997e7.json.br +0 -0
  90. package/lib/endfield-map/resources/map-a4e8ef07159997e7.json.gz +0 -0
  91. package/lib/endfield-map/resources/map-ab35d06875ee418f.png +0 -0
  92. package/lib/endfield-map/resources/map-ac51bb3e7623cd8b.png +0 -0
  93. package/lib/endfield-map/resources/map-ac830a66296bc83a.png +0 -0
  94. package/lib/endfield-map/resources/map-b3fd04209718e55e.json.br +0 -0
  95. package/lib/endfield-map/resources/map-b3fd04209718e55e.json.gz +0 -0
  96. package/lib/endfield-map/resources/map-b8164f1af7c5d895.png +0 -0
  97. package/lib/endfield-map/resources/map-b8cde93b965384d8.json.br +0 -0
  98. package/lib/endfield-map/resources/map-b8cde93b965384d8.json.gz +0 -0
  99. package/lib/endfield-map/resources/map-b8e48511502e0e8d.json.br +0 -0
  100. package/lib/endfield-map/resources/map-b8e48511502e0e8d.json.gz +0 -0
  101. package/lib/endfield-map/resources/map-bfc58277d5ba649d.png +0 -0
  102. package/lib/endfield-map/resources/map-c013f82048c6891a.png +0 -0
  103. package/lib/endfield-map/resources/map-c49c947841ae4ab5.json.br +0 -0
  104. package/lib/endfield-map/resources/map-c49c947841ae4ab5.json.gz +0 -0
  105. package/lib/endfield-map/resources/map-c4d1b1c6d6706c81.json.br +0 -0
  106. package/lib/endfield-map/resources/map-c4d1b1c6d6706c81.json.gz +0 -0
  107. package/lib/endfield-map/resources/map-c607f8b4c8ad8967.png +0 -0
  108. package/lib/endfield-map/resources/map-c6f20276b8781326.json.br +0 -0
  109. package/lib/endfield-map/resources/map-c6f20276b8781326.json.gz +0 -0
  110. package/lib/endfield-map/resources/map-d51adf5952af1e8e.png +0 -0
  111. package/lib/endfield-map/resources/map-d695eface22f8b74.json.br +0 -0
  112. package/lib/endfield-map/resources/map-d695eface22f8b74.json.gz +0 -0
  113. package/lib/endfield-map/resources/map-db11d394456a7465.png +0 -0
  114. package/lib/endfield-map/resources/map-dcc4d218581e3dc6.png +0 -0
  115. package/lib/endfield-map/resources/map-ddd43dbb8ab8a4e3.json.br +0 -0
  116. package/lib/endfield-map/resources/map-ddd43dbb8ab8a4e3.json.gz +0 -0
  117. package/lib/endfield-map/resources/map-defe5db7dabf748a.png +0 -0
  118. package/lib/endfield-map/resources/map-e1aeb833e22520aa.json.br +0 -0
  119. package/lib/endfield-map/resources/map-e1aeb833e22520aa.json.gz +0 -0
  120. package/lib/endfield-map/resources/map-e25ee9af6e493500.png +0 -0
  121. package/lib/endfield-map/resources/map-e3824082cd6f747d.json.br +0 -0
  122. package/lib/endfield-map/resources/map-e3824082cd6f747d.json.gz +0 -0
  123. package/lib/endfield-map/resources/map-e694a91e630b1bd6.png +0 -0
  124. package/lib/endfield-map/resources/map-eaa93af79d12a87d.png +0 -0
  125. package/lib/endfield-map/resources/map-ec9575ee65d7766f.json.br +0 -0
  126. package/lib/endfield-map/resources/map-ec9575ee65d7766f.json.gz +0 -0
  127. package/lib/endfield-map/resources/map-ede421a329dc78a4.png +0 -0
  128. package/lib/endfield-map/resources/map-eff3bce8f6888fc5.json.br +0 -0
  129. package/lib/endfield-map/resources/map-eff3bce8f6888fc5.json.gz +0 -0
  130. package/lib/endfield-map/resources/map-fb5f4a59a181452c.png +0 -0
  131. package/lib/endfield-map/resources/map-fdeaa17eeba90d8e.json.br +0 -0
  132. package/lib/endfield-map/resources/map-fdeaa17eeba90d8e.json.gz +0 -0
  133. package/lib/rhine/assets/archive-assembly.glb +0 -0
  134. package/lib/rhine/assets/archive-cassette.glb +0 -0
  135. package/lib/rhine/fonts/MiSans-Bold.woff2 +0 -0
  136. package/lib/rhine/fonts/MiSans-Demibold.woff2 +0 -0
  137. package/lib/rhine/fonts/MiSans-Light.woff2 +0 -0
  138. package/lib/rhine/fonts/MiSans-Regular.woff2 +0 -0
  139. package/lib/rhine/fonts/MiSans-license.pdf +0 -0
  140. package/lib/rhine/fonts/NOTICE.txt +6 -0
  141. package/lib/rhine/licenses/RhineLabUI-MIT.txt +21 -0
  142. package/lib/rhine/licenses/entities-BSD-2-Clause.txt +11 -0
  143. package/lib/rhine/licenses/linkify-it-MIT.txt +22 -0
  144. package/lib/rhine/licenses/markdown-it-MIT.txt +22 -0
  145. package/lib/rhine/licenses/mdurl-MIT.txt +45 -0
  146. package/lib/rhine/licenses/punycode.js-MIT.txt +20 -0
  147. package/lib/rhine/licenses/rolling-number-MIT.txt +21 -0
  148. package/lib/rhine/licenses/three-MIT.txt +21 -0
  149. package/lib/rhine/licenses/uc.micro-MIT.txt +20 -0
  150. package/lib/rhine/rhine.css +1 -0
  151. package/lib/rhine/rhine.js +5106 -0
  152. package/lib/skins/common.css +31 -0
  153. package/lib/skins/endfield-aic.css +141 -0
  154. package/lib/skins/prts-agent.css +136 -0
  155. package/lib/skins/rhine-lab.css +70 -0
  156. package/package.json +112 -0
  157. package/presets/definition.js +26 -0
  158. package/presets/prts/agent.cordis.yml +25 -0
  159. package/presets/prts/preset.yml +3 -0
  160. package/presets/register.js +86 -0
  161. package/resources/entity-routing.json +21 -0
  162. package/skills/prts-investigation/SKILL.md +121 -0
  163. package/skills/prts-retrieval/SKILL.md +35 -0
  164. package/skills/prts-retrieval/references/cloud-tools.md +13 -0
  165. package/skills/prts-retrieval/references/diagnostics.md +15 -0
  166. package/skills/prts-retrieval/references/local-tools.md +24 -0
  167. package/skills/prts-retrieval/references/module-arknights.md +23 -0
  168. package/skills/prts-retrieval/references/module-dual.md +15 -0
  169. package/skills/prts-retrieval/references/module-endfield.md +21 -0
  170. package/skills/prts-retrieval/references/retrieval-process.md +44 -0
  171. package/skills/prts-retrieval/references/retrieval-recipes-dual.md +34 -0
  172. package/skills/prts-retrieval/references/retrieval-recipes-endfield.md +37 -0
  173. package/skills/prts-retrieval/references/retrieval-recipes.md +39 -0
  174. package/skills/prts-retrieval/references/tools-runtime.md +48 -0
  175. package/skills/prts-retrieval/references/wiki-schema.md +38 -0
  176. package/src/archive-presentation.js +287 -0
  177. package/src/cloud-projection.js +124 -0
  178. package/src/cloud.js +498 -0
  179. package/src/entity-recognizer.js +311 -0
  180. package/src/entity-routing.js +82 -0
  181. package/src/evidence-state.js +332 -0
  182. package/src/i18n.js +365 -0
  183. package/src/index.js +1261 -0
  184. package/src/installer.js +1362 -0
  185. package/src/investigation-review.js +87 -0
  186. package/src/investigation-store.js +579 -0
  187. package/src/investigation-tools.js +113 -0
  188. package/src/localization-format.js +23 -0
  189. package/src/manual-search.js +103 -0
  190. package/src/read.js +1097 -0
  191. package/src/release-compatibility.js +73 -0
  192. package/src/release-watch.js +40 -0
  193. package/src/search-projection.js +139 -0
  194. package/src/search.js +1522 -0
  195. package/src/skill.js +98 -0
  196. package/src/source-map.js +348 -0
  197. package/src/state.js +314 -0
  198. package/src/store.js +1569 -0
  199. package/src/timeline.js +386 -0
  200. package/src/ui.js +1353 -0
  201. package/src/wiki.js +111 -0
  202. package/tsconfig.rhine.json +15 -0
  203. package/ui/rhine/archive-navigation-limit.ts +31 -0
  204. package/ui/rhine/archive-rack.ts +83 -0
  205. package/ui/rhine/assets/evidence-observatory.webp +0 -0
  206. package/ui/rhine/board-plane-transform.ts +24 -0
  207. package/ui/rhine/catalogue.ts +72 -0
  208. package/ui/rhine/deferred-preparation.ts +146 -0
  209. package/ui/rhine/evidence-board-controls.css +22 -0
  210. package/ui/rhine/evidence-board-history.ts +73 -0
  211. package/ui/rhine/evidence-board-model.ts +189 -0
  212. package/ui/rhine/evidence-board-panel.ts +762 -0
  213. package/ui/rhine/evidence-board-scene.ts +548 -0
  214. package/ui/rhine/evidence-board.css +191 -0
  215. package/ui/rhine/evidence-card-art.ts +470 -0
  216. package/ui/rhine/evidence-card-style.ts +17 -0
  217. package/ui/rhine/evidence-inbox-scene.ts +43 -0
  218. package/ui/rhine/evidence-inbox.css +29 -0
  219. package/ui/rhine/evidence-inbox.ts +104 -0
  220. package/ui/rhine/index.ts +12 -0
  221. package/ui/rhine/interaction.css +20 -0
  222. package/ui/rhine/investigation-board-plane.ts +5 -0
  223. package/ui/rhine/investigation-board.css +34 -0
  224. package/ui/rhine/investigation-board.ts +348 -0
  225. package/ui/rhine/investigation-edits.ts +46 -0
  226. package/ui/rhine/investigation-layout.ts +29 -0
  227. package/ui/rhine/label-prefetch.ts +34 -0
  228. package/ui/rhine/looping-title.ts +40 -0
  229. package/ui/rhine/manual-search.css +41 -0
  230. package/ui/rhine/manual-search.ts +89 -0
  231. package/ui/rhine/original/appearance.ts +180 -0
  232. package/ui/rhine/original/archive-label.ts +84 -0
  233. package/ui/rhine/original/archive-lighting.ts +39 -0
  234. package/ui/rhine/original/archive-loop.ts +82 -0
  235. package/ui/rhine/original/archive-refill.ts +64 -0
  236. package/ui/rhine/original/array-detail-data.ts +2 -0
  237. package/ui/rhine/original/array-detail.ts +41 -0
  238. package/ui/rhine/original/array-interior.ts +411 -0
  239. package/ui/rhine/original/array-occlusion.ts +166 -0
  240. package/ui/rhine/original/array-shell.ts +38 -0
  241. package/ui/rhine/original/array-visibility.ts +309 -0
  242. package/ui/rhine/original/asset-url.ts +4 -0
  243. package/ui/rhine/original/brand.ts +12 -0
  244. package/ui/rhine/original/cassette-lod.ts +81 -0
  245. package/ui/rhine/original/data.ts +21 -0
  246. package/ui/rhine/original/decryption.ts +185 -0
  247. package/ui/rhine/original/detail-signature.ts +18 -0
  248. package/ui/rhine/original/glass-reveal.ts +45 -0
  249. package/ui/rhine/original/internal-optics.ts +70 -0
  250. package/ui/rhine/original/label-renderer.ts +119 -0
  251. package/ui/rhine/original/label-textures.ts +117 -0
  252. package/ui/rhine/original/model-viewer.ts +658 -0
  253. package/ui/rhine/original/motion.ts +116 -0
  254. package/ui/rhine/original/opaque-backfaces.ts +64 -0
  255. package/ui/rhine/original/program-preparation.ts +111 -0
  256. package/ui/rhine/original/quality-renderer.ts +97 -0
  257. package/ui/rhine/original/quality-settings.ts +111 -0
  258. package/ui/rhine/original/render-quality.ts +163 -0
  259. package/ui/rhine/original/scene.ts +2051 -0
  260. package/ui/rhine/original/shelf-interior.ts +142 -0
  261. package/ui/rhine/original/ui-transitions.ts +111 -0
  262. package/ui/rhine/original/viewer-camera.ts +92 -0
  263. package/ui/rhine/original/viewport-layout.ts +41 -0
  264. package/ui/rhine/original-ui.css +2334 -0
  265. package/ui/rhine/readability.css +46 -0
  266. package/ui/rhine/reading-object.ts +96 -0
  267. package/ui/rhine/reading-origin.ts +28 -0
  268. package/ui/rhine/reading-room.css +55 -0
  269. package/ui/rhine/report-markdown.ts +98 -0
  270. package/ui/rhine/scene-edge-navigation.ts +84 -0
  271. package/ui/rhine/scene.ts +1726 -0
  272. package/ui/rhine/shelf-layout.ts +38 -0
  273. package/ui/rhine/snapshot-update-gate.ts +22 -0
  274. package/ui/rhine/source-reading.ts +15 -0
  275. package/ui/rhine/source-selection.css +164 -0
  276. package/ui/rhine/temporary-performance-observers.ts +163 -0
  277. package/ui/rhine/temporary-performance-panel.ts +124 -0
  278. package/ui/rhine/temporary-performance.css +19 -0
  279. package/ui/rhine/temporary-performance.ts +552 -0
  280. package/ui/rhine/temporary-render-diagnostics.ts +105 -0
  281. package/ui/rhine/tool-activity.ts +69 -0
  282. package/ui/rhine/types.ts +219 -0
  283. package/ui/rhine/visible-scene-hit.ts +13 -0
  284. package/ui/rhine/workbench.css +818 -0
  285. package/ui/rhine/workbench.ts +2245 -0
  286. package/ui/rhine/workspace-travel.ts +32 -0
  287. package/vite.rhine.config.ts +19 -0
@@ -0,0 +1,311 @@
1
+ /**
2
+ * 用户问题的实体别名预识别。匹配器与浏览器端 EntityAliasAutomaton
3
+ * 同构,数据源复用 entities 投影与 char_alias.txt。
4
+ *
5
+ * 这一层只把规范实体提示给 Agent,不改写工具参数、不自动加过滤器,
6
+ * 也不维护候选排序或检索进度。
7
+ */
8
+ import { buildAliasGroups } from './timeline.js'
9
+ import { loadEntityRelationCatalog } from './entity-routing.js'
10
+
11
+ const preparedRecognizers = new WeakMap()
12
+
13
+ const GAME_LABELS = Object.freeze({ arknights: '明日方舟', endfield: '终末地' })
14
+
15
+ // 实体表属于可更新资料,不是受信任的 prompt。所有进入 plugin user notice 的
16
+ // 字段必须保持单行、限长并转义标签边界,避免恶意资料闭合结构标签后伪造指令。
17
+ const promptData = (value, maximum = 160) => [...String(value ?? '')
18
+ .replace(/[\u0000-\u001f\u007f]+/gu, ' ').replace(/\s+/gu, ' ').trim()]
19
+ .slice(0, maximum).join('')
20
+ .replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;')
21
+ .replaceAll('"', '&quot;').replaceAll("'", '&#39;')
22
+
23
+ function cancelledError() {
24
+ return Object.assign(new Error('实体预识别已取消'), { code: 'CANCELLED' })
25
+ }
26
+
27
+ function waitForPreparation(promise, signal) {
28
+ if (!signal) return promise
29
+ if (signal.aborted) return Promise.reject(cancelledError())
30
+ return new Promise((resolve, reject) => {
31
+ let settled = false
32
+ const finish = (callback, value) => {
33
+ if (settled) return
34
+ settled = true
35
+ signal.removeEventListener('abort', onAbort)
36
+ callback(value)
37
+ }
38
+ const onAbort = () => finish(reject, cancelledError())
39
+ signal.addEventListener('abort', onAbort, { once: true })
40
+ promise.then(
41
+ (value) => finish(resolve, value),
42
+ (error) => finish(reject, error),
43
+ )
44
+ })
45
+ }
46
+
47
+ function normalized(value) {
48
+ return String(value || '').normalize('NFKC').replace(/\s+/gu, ' ').trim().toLocaleLowerCase()
49
+ }
50
+
51
+ /** Browser 同源的 Aho-Corasick 实体别名匹配器。 */
52
+ export class EntityAliasAutomaton {
53
+ constructor(groups = []) {
54
+ this.nodes = [{ next: new Map(), fail: 0, outputs: [] }]
55
+ for (const group of groups) {
56
+ const canonical = String(group.canonical || '').trim()
57
+ for (const rawAlias of group.aliases || []) {
58
+ const alias = normalized(rawAlias)
59
+ if (!canonical || !alias) continue
60
+ let node = 0
61
+ for (const character of alias) {
62
+ if (!this.nodes[node].next.has(character)) {
63
+ this.nodes[node].next.set(character, this.nodes.length)
64
+ this.nodes.push({ next: new Map(), fail: 0, outputs: [] })
65
+ }
66
+ node = this.nodes[node].next.get(character)
67
+ }
68
+ this.nodes[node].outputs.push({ canonical, alias: String(rawAlias).trim(),
69
+ games: [...new Set(group.games || [])], length: [...alias].length })
70
+ }
71
+ }
72
+ const queue = []
73
+ for (const child of this.nodes[0].next.values()) queue.push(child)
74
+ for (let cursor = 0; cursor < queue.length; cursor += 1) {
75
+ const parent = queue[cursor]
76
+ for (const [character, child] of this.nodes[parent].next) {
77
+ queue.push(child)
78
+ let failure = this.nodes[parent].fail
79
+ while (failure && !this.nodes[failure].next.has(character)) failure = this.nodes[failure].fail
80
+ this.nodes[child].fail = this.nodes[failure].next.get(character) ?? 0
81
+ this.nodes[child].outputs.push(...this.nodes[this.nodes[child].fail].outputs)
82
+ }
83
+ }
84
+ }
85
+
86
+ match(text) {
87
+ const characters = [...normalized(text)]
88
+ const found = []
89
+ let node = 0
90
+ for (let index = 0; index < characters.length; index += 1) {
91
+ const character = characters[index]
92
+ while (node && !this.nodes[node].next.has(character)) node = this.nodes[node].fail
93
+ node = this.nodes[node].next.get(character) ?? 0
94
+ for (const output of this.nodes[node].outputs) {
95
+ const start = index - output.length + 1
96
+ // 单字别名在普通句子中误报率过高;仅当整个输入就是该字时保留。
97
+ if (output.length === 1 && characters.length > 1) continue
98
+ found.push({ canonical: output.canonical, alias: output.alias, games: output.games,
99
+ start, end: index + 1 })
100
+ }
101
+ }
102
+ found.sort((left, right) => left.start - right.start
103
+ || (right.end - right.start) - (left.end - left.start)
104
+ || left.canonical.localeCompare(right.canonical, 'zh-CN'))
105
+ const selected = []
106
+ for (const match of found) {
107
+ if (selected.some((item) => match.start >= item.start && match.end <= item.end
108
+ && !(match.start === item.start && match.end === item.end))) continue
109
+ if (selected.some((item) => match.start === item.start && match.end === item.end
110
+ && match.canonical === item.canonical)) continue
111
+ selected.push(match)
112
+ }
113
+ return selected
114
+ }
115
+ }
116
+
117
+ /** 模式准入阶段预热并按 store + dataVersion 共享只读 AC 自动机。 */
118
+ export async function prepareEntityRecognition(store, { signal } = {}) {
119
+ if (signal?.aborted) throw cancelledError()
120
+ await waitForPreparation(store.ready(), signal)
121
+ const dataVersion = store.dataVersion
122
+ let state = preparedRecognizers.get(store)
123
+ if (!state || state.dataVersion !== dataVersion) {
124
+ state = { dataVersion, automaton: null, promise: null }
125
+ state.promise = Promise.resolve(store._aliasGroups || buildAliasGroups(store))
126
+ .then((groups) => {
127
+ if (store.dataVersion !== dataVersion) {
128
+ if (preparedRecognizers.get(store) === state) preparedRecognizers.delete(store)
129
+ return prepareEntityRecognition(store)
130
+ }
131
+ store._aliasGroups ||= groups
132
+ state.automaton = new EntityAliasAutomaton(groups)
133
+ return state.automaton
134
+ })
135
+ .catch((error) => {
136
+ if (preparedRecognizers.get(store) === state) preparedRecognizers.delete(store)
137
+ throw error
138
+ })
139
+ preparedRecognizers.set(store, state)
140
+ }
141
+ const automaton = await waitForPreparation(
142
+ state.automaton ? Promise.resolve(state.automaton) : state.promise, signal)
143
+ // 关系表与别名自动机一起预热,才能在 inbox claim 与 prompt assembly
144
+ // 之间的同步窗口内生成完整的本轮动态上下文。
145
+ await waitForPreparation(loadEntityRelationCatalog(store), signal)
146
+ return automaton
147
+ }
148
+
149
+ export function isEntityRecognitionReady(store) {
150
+ const state = preparedRecognizers.get(store)
151
+ return Boolean(store.loaded && state?.dataVersion === store.dataVersion && state.automaton)
152
+ }
153
+
154
+ /** 资料版本变化后自动复用或重建 AC 自动机。 */
155
+ export function createEntityRecognizer(store) {
156
+ return {
157
+ async detect(text, { signal } = {}) {
158
+ const automaton = await prepareEntityRecognition(store, { signal })
159
+ if (signal?.aborted) throw cancelledError()
160
+ const catalog = await loadEntityRelationCatalog(store)
161
+ if (signal?.aborted) throw cancelledError()
162
+ return detectEntities(automaton, catalog, text)
163
+ }
164
+ }
165
+ }
166
+
167
+ function detectEntities(automaton, catalog, text) {
168
+ const matches = automaton.match(text)
169
+ const normalizedText = normalized(text)
170
+ const relationHints = []
171
+ for (const row of catalog.retravelers || []) {
172
+ const names = [row.endfield_name, row.terra_memory_prototype].filter(Boolean)
173
+ if (names.some((name) => normalizedText.includes(normalized(name)))) {
174
+ relationHints.push({ kind: 'retraveler_memory_prototype', ...row,
175
+ query_terms: [...new Set([...names, '再旅者', '记忆原型'])] })
176
+ }
177
+ }
178
+ for (const row of catalog.visual_parallels_without_lore_relation || []) {
179
+ const names = [row.endfield_name, row.arknights_name].filter(Boolean)
180
+ if (names.some((name) => normalizedText.includes(normalized(name)))) {
181
+ relationHints.push({ kind: 'visual_parallel_without_lore_relation', ...row,
182
+ query_terms: names })
183
+ }
184
+ }
185
+ return { matches, entities: [...new Set(matches.map((item) => item.canonical))],
186
+ relation_hints: relationHints }
187
+ }
188
+
189
+ /** 仅在后台预热已经完成时同步识别;prompt assembly 不能等待异步 I/O。 */
190
+ function detectPreparedEntities(store, text) {
191
+ const state = preparedRecognizers.get(store)
192
+ const catalog = store?._endfieldRelationCatalog
193
+ if (!store?.loaded || !state?.automaton || state.dataVersion !== store.dataVersion
194
+ || catalog?.dataVersion !== store.dataVersion) return null
195
+ return detectEntities(state.automaton, catalog.value, text)
196
+ }
197
+
198
+ function messageText(message) {
199
+ if (message?.source?.kind !== 'user') return ''
200
+ return (message.content || []).filter((block) => block?.type === 'text')
201
+ .map((block) => String(block.text || '')).filter(Boolean).join('\n')
202
+ }
203
+
204
+ function recognitionContext(result, enabledGames) {
205
+ const aliases = new Map()
206
+ for (const match of result.matches) {
207
+ const value = aliases.get(match.canonical) || { aliases: new Set(), games: new Set() }
208
+ value.aliases.add(match.alias)
209
+ for (const game of match.games || []) value.games.add(game)
210
+ aliases.set(match.canonical, value)
211
+ }
212
+ for (const hint of result.relation_hints || []) {
213
+ if (hint.endfield_name) {
214
+ const value = aliases.get(hint.endfield_name) || { aliases: new Set(), games: new Set() }
215
+ value.aliases.add(hint.endfield_name)
216
+ value.games.add('endfield')
217
+ aliases.set(hint.endfield_name, value)
218
+ }
219
+ const arknightsName = hint.terra_memory_prototype || hint.arknights_name
220
+ if (arknightsName) {
221
+ const value = aliases.get(arknightsName) || { aliases: new Set(), games: new Set() }
222
+ value.aliases.add(arknightsName)
223
+ value.games.add('arknights')
224
+ aliases.set(arknightsName, value)
225
+ }
226
+ }
227
+ const lines = [...aliases].map(([canonical, value]) => {
228
+ const ownership = [...value.games].map((game) => GAME_LABELS[game]).filter(Boolean)
229
+ return `- ${promptData(canonical)} — ${ownership.length ? ownership.join(' + ') : '归属未确定'}` +
230
+ `(问题中命中:${[...value.aliases].map((alias) => promptData(alias)).join('、')})`
231
+ })
232
+ const enabled = enabledGames.map((game) => GAME_LABELS[game]).filter(Boolean)
233
+ const relationLines = (result.relation_hints || []).map((hint) =>
234
+ hint.kind === 'retraveler_memory_prototype'
235
+ ? `- ${promptData(hint.endfield_name)}:再旅者;泰拉记忆原型=${promptData(hint.terra_memory_prototype || '未登记')}。检索词:${(Array.isArray(hint.query_terms) ? hint.query_terms : []).map((term) => promptData(term)).join('、')}。两者不是别名。`
236
+ : `- ${promptData(hint.endfield_name)} / ${promptData(hint.arknights_name)}:仅登记外观相似;现有剧情没有关系证据,不得推断为再旅者或记忆原型。`)
237
+ return [
238
+ '<prts:retrieval-context>',
239
+ `当前搭载资料:${enabled.join('、') || '无'}。`,
240
+ ...(lines.length ? ['用户问题中识别到的规范实体与游戏归属:', ...lines] : []),
241
+ ...relationLines,
242
+ '</prts:retrieval-context>',
243
+ ].join('\n')
244
+ }
245
+
246
+ function skillArguments(event) {
247
+ if (event?.type !== 'tool/call' || event.data?.name !== 'skill') return null
248
+ try { return JSON.parse(event.data.arguments) }
249
+ catch { return null }
250
+ }
251
+
252
+ function hasVisibleRetrievalSkill(agent, messages) {
253
+ if ((messages || []).some((message) => message?.source?.kind === 'skill-invocation'
254
+ && message.source.name === 'prts-retrieval')) return true
255
+ const session = agent?.session
256
+ if (typeof session?.snapshotEvents !== 'function') return false
257
+ const events = session.snapshotEvents()
258
+ const visible = new Set(session.surface?.nodes || [])
259
+ const calls = new Set(events.filter((event) => {
260
+ const args = skillArguments(event)
261
+ return args?.name === 'prts-retrieval'
262
+ }).map((event) => event.data.callId))
263
+ return events.some((event) => visible.has(event.seq) && (
264
+ (event.type === 'user/message' && event.data?.source?.kind === 'skill-invocation'
265
+ && event.data.source.name === 'prts-retrieval')
266
+ || (event.type === 'tool/result' && calls.has(event.data?.message?.source?.callId)
267
+ && event.data.message.content[0].isError !== true)))
268
+ }
269
+
270
+ /**
271
+ * 把当前用户问题的实体结果贡献给 DSH 动态上下文快照。
272
+ *
273
+ * inbox claim 发生在 prompt assembly 之前,因此预热完成时可以同步切换到
274
+ * 本轮实体;DSH 的 runtime-context 投影负责让新快照取代旧快照。Skill
275
+ * 尚未加载时 provider 返回空串,实体块会等到 Skill 结果可见后的下一步。
276
+ */
277
+ export function applyEntityRecognition(ctx, store, shared = null) {
278
+ if (typeof ctx.on !== 'function' || typeof ctx.systemPrompt?.context !== 'function') return false
279
+ const recognizer = createEntityRecognizer(store)
280
+ const turns = new WeakMap()
281
+ ctx.on('agent/inbox/claimed', ({ agent, message, turn }) => {
282
+ const text = messageText(message)
283
+ if (!text) return
284
+ const state = { turn, text, context: '' }
285
+ turns.set(agent, state)
286
+ const enabledGames = () => shared?.effective?.().enabledGames || ['arknights', 'endfield']
287
+ const prepared = detectPreparedEntities(store, text)
288
+ if (prepared) {
289
+ state.context = recognitionContext(prepared, enabledGames())
290
+ return
291
+ }
292
+ // 冷启动只影响当前第一次 assembly;异步结果会在本轮的下一次步骤或
293
+ // 下一轮 assembly 生效。正常 Web 部署会在首问前完成后台预热。
294
+ recognizer.detect(text).then((result) => {
295
+ if (turns.get(agent) === state) state.context = recognitionContext(result, enabledGames())
296
+ }).catch((error) => {
297
+ if (turns.get(agent) !== state || error?.code === 'CANCELLED') return
298
+ ctx.logger?.warn?.(`prts-corpus: 实体预识别失败,已跳过: ${error?.message ?? error}`)
299
+ state.context = ''
300
+ })
301
+ })
302
+ ctx.systemPrompt.context({
303
+ name: 'prts-terrarchive:retrieval-entities',
304
+ order: 1000,
305
+ text: ({ scope }) => {
306
+ const state = turns.get(scope)
307
+ return state?.context && hasVisibleRetrievalSkill(scope, []) ? state.context : ''
308
+ },
309
+ })
310
+ return true
311
+ }
@@ -0,0 +1,82 @@
1
+ import { readFile } from 'node:fs/promises'
2
+
3
+ const bundledRelationsUrl = new URL('../resources/entity-routing.json', import.meta.url)
4
+ let bundledRelationsPromise = null
5
+
6
+ async function bundledRelations() {
7
+ bundledRelationsPromise ||= readFile(bundledRelationsUrl, 'utf8')
8
+ .then((text) => JSON.parse(text)).catch(() => ({}))
9
+ return bundledRelationsPromise
10
+ }
11
+
12
+ function mergeRelationCatalog(fallback, installed) {
13
+ const mergeRows = (field, identity) => {
14
+ const rows = new Map()
15
+ for (const row of [...(fallback?.[field] || []), ...(installed?.[field] || [])]) {
16
+ const key = identity(row)
17
+ if (key) rows.set(key, row)
18
+ }
19
+ return [...rows.values()]
20
+ }
21
+ return {
22
+ retravelers: mergeRows('retravelers', (row) => String(row?.endfield_name || '')),
23
+ visual_parallels_without_lore_relation: mergeRows(
24
+ 'visual_parallels_without_lore_relation',
25
+ (row) => `${row?.endfield_name || ''}\0${row?.arknights_name || ''}`,
26
+ ),
27
+ }
28
+ }
29
+
30
+ /** 资料包内关系表优先,插件内置表只为旧包提供最小兼容路由。 */
31
+ export async function loadEntityRelationCatalog(store) {
32
+ const dataVersion = store?.dataVersion ?? 'no-store'
33
+ const cached = store?._endfieldRelationCatalog
34
+ if (cached?.dataVersion === dataVersion) return cached.value
35
+ let installed = {}
36
+ try {
37
+ const loaded = await store?.getDocumentByPath?.('config/retravelers.json')
38
+ const record = loaded?.record || loaded
39
+ installed = JSON.parse((record?.lines || []).map((line) => line.text).join('\n') || '{}')
40
+ } catch { installed = {} }
41
+ const value = mergeRelationCatalog(await bundledRelations(), installed)
42
+ if (store) store._endfieldRelationCatalog = { dataVersion, value }
43
+ return value
44
+ }
45
+
46
+ export function relationEndfieldNames(catalog) {
47
+ return new Set((catalog?.retravelers || [])
48
+ .map((row) => String(row?.endfield_name || '').trim()).filter(Boolean))
49
+ }
50
+
51
+ function normalized(value) {
52
+ return String(value || '').normalize('NFKC').replace(/\s+/gu, ' ').trim().toLocaleLowerCase()
53
+ }
54
+
55
+ /** 找出本次请求或可见命中涉及的再旅者;它们是关系,不进入普通 aliases。 */
56
+ export function relevantRetravelerRelations(catalog, ...values) {
57
+ const text = normalized(values.map((value) => {
58
+ try { return typeof value === 'string' ? value : JSON.stringify(value) }
59
+ catch { return '' }
60
+ }).join('\n'))
61
+ if (!text) return []
62
+ return (catalog?.retravelers || []).filter((row) => {
63
+ const names = [row?.endfield_name, row?.terra_memory_prototype].map(normalized).filter(Boolean)
64
+ return names.some((name) => text.includes(name))
65
+ }).map((row) => ({
66
+ relation_kind: 'endfield_retraveler_memory_prototype',
67
+ endfield_name: String(row.endfield_name),
68
+ ...(row.terra_memory_prototype
69
+ ? { terra_memory_prototype: String(row.terra_memory_prototype) } : {}),
70
+ relation_status: String(row.relation_status || 'reviewed'),
71
+ not_alias: true,
72
+ }))
73
+ }
74
+
75
+ /** 仅双模块启用时,把人工审校关系作为独立附属字段挂到检索结果。 */
76
+ export async function attachRetravelerRelations(store, response, request, enabledGames) {
77
+ if (!enabledGames?.includes('arknights') || !enabledGames?.includes('endfield')
78
+ || response?.status === 'error' || response?.error) return response
79
+ const catalog = await loadEntityRelationCatalog(store)
80
+ const relations = relevantRetravelerRelations(catalog, request, response?.documents)
81
+ return relations.length ? { ...response, retraveler_relations: relations } : response
82
+ }