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
package/src/skill.js ADDED
@@ -0,0 +1,98 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { homedir } from 'node:os'
4
+ import { join, resolve } from 'node:path'
5
+
6
+ export const name = 'prts-retrieval-skill'
7
+ export const inject = ['skills']
8
+
9
+ const skillDirectoryUrl = new URL('../skills/prts-retrieval/', import.meta.url)
10
+ const skillFileUrl = new URL('SKILL.md', skillDirectoryUrl)
11
+ const referenceUrl = (name) => new URL(`references/${name}`, skillDirectoryUrl)
12
+
13
+ const GAME_LABELS = Object.freeze({ arknights: '明日方舟', endfield: '明日方舟:终末地' })
14
+
15
+ function normalizedGames(value) {
16
+ if (!Array.isArray(value)) return null
17
+ const games = [...new Set(value.filter((game) => game === 'arknights' || game === 'endfield'))]
18
+ return games.length ? games : null
19
+ }
20
+
21
+ /**
22
+ * Skill 与工具插件是两个 Cordis entry,不共享内存状态。在会话创建时
23
+ * 合并 Skill entry 的基础范围与同一份用户配置,使正文模块和工具范围一致。
24
+ * 实体识别会在本 Skill 加载后注入当前问题的短提示;工具实例的实时范围
25
+ * 用于覆盖会话创建后的热更改。
26
+ */
27
+ export async function configuredSkillGames(baseConfig = {}) {
28
+ const configuredHome = process.env.DSH_HOME?.trim()
29
+ const dshHome = resolve(configuredHome || join(homedir(), '.dsh'))
30
+ try {
31
+ const config = JSON.parse(await readFile(join(dshHome, 'prts-corpus.json'), 'utf8'))
32
+ const explicit = normalizedGames(config.enabledGames)
33
+ if (explicit) return explicit
34
+ if (config.cloudGame === 'arknights' || config.cloudGame === 'endfield') return [config.cloudGame]
35
+ } catch (error) {
36
+ if (error?.code !== 'ENOENT') {
37
+ // 无效配置由工具插件给出权威错误;Skill 这里保守回退双游戏。
38
+ }
39
+ }
40
+ return normalizedGames(baseConfig.enabledGames)
41
+ || (baseConfig.cloudGame === 'arknights' || baseConfig.cloudGame === 'endfield'
42
+ ? [baseConfig.cloudGame] : null)
43
+ || ['arknights', 'endfield']
44
+ }
45
+
46
+ export function skillDescription(games) {
47
+ void games
48
+ // Catalog 描述负责发现能力,不能随当前开关隐藏另一款游戏;否则用户询问
49
+ // 尚未启用的资料库时,Skill 反而无法加载并说明正确的恢复路径。
50
+ return '使用 PRTS.chat 本地与云端资料检索、核验并回答《明日方舟》《明日方舟:终末地》及跨游戏关系问题;适用于剧情、人物、设定、台词、档案、Wiki 与时间线研究。'
51
+ }
52
+
53
+ function skillBody(source) {
54
+ const match = /^---\r?\n[\s\S]*?\r?\n---\r?\n([\s\S]*)$/.exec(source)
55
+ if (!match) throw new Error('prts-retrieval/SKILL.md 缺少有效 YAML frontmatter')
56
+ return match[1].trim()
57
+ }
58
+
59
+ /** @param {import('@deepseek-ai/cordis').Context} ctx */
60
+ export async function apply(ctx, config = {}) {
61
+ const games = await configuredSkillGames(config ?? {})
62
+ const scope = games.map((game) => GAME_LABELS[game]).join('、')
63
+ const moduleFiles = games.map((game) => `module-${game}.md`)
64
+ if (games.length === 2) moduleFiles.push('module-dual.md')
65
+ // 双模块已有联合路由和两份模块速查,不再重复装入两套单游戏详细配方。
66
+ const recipeFiles = games.length === 2
67
+ ? ['retrieval-recipes-dual.md']
68
+ : [games[0] === 'arknights' ? 'retrieval-recipes.md' : 'retrieval-recipes-endfield.md']
69
+ const moduleBodies = await Promise.all(moduleFiles.map((file) => readFile(referenceUrl(file), 'utf8')))
70
+ const [toolsBody, processBody, recipeBodies] = await Promise.all([
71
+ readFile(referenceUrl('tools-runtime.md'), 'utf8'),
72
+ readFile(referenceUrl('retrieval-process.md'), 'utf8'),
73
+ Promise.all(recipeFiles.map((file) => readFile(referenceUrl(file), 'utf8'))),
74
+ ])
75
+ const content = [
76
+ `## 本次会话的资料范围\n\n会话创建时启用:**${scope}**。` +
77
+ '\n`<prts:retrieval-context>` 只适用于当前动态上下文快照对应的用户问题;新快照会取代旧快照,不得把前一轮的实体或关系提示沿用到新问题。块内实时搭载范围与实体归属优先级更高。',
78
+ skillBody(await readFile(skillFileUrl, 'utf8')),
79
+ ...moduleBodies.map((body) => body.trim()),
80
+ toolsBody.trim(),
81
+ processBody.trim(),
82
+ ...recipeBodies.map((body) => body.trim()),
83
+ ].join('\n\n').replace(/\r\n/g, '\n')
84
+ const retrieval = ctx.skills.register({
85
+ name: 'prts-retrieval',
86
+ description: skillDescription(games),
87
+ source: 'bundled',
88
+ provider: 'prts-terrarchive',
89
+ resourceBase: { kind: 'directory', path: fileURLToPath(skillDirectoryUrl) },
90
+ content,
91
+ })
92
+ const directory = new URL('../skills/prts-investigation/', import.meta.url)
93
+ const investigation = ctx.skills.register({ name: 'prts-investigation',
94
+ description: '持续组织研究调查:由 LLM 决定新建或延续调查板,边检索边将重点材料放入独立证据盒,整理上板、维护证据关系、发布可追溯的中央报告。',
95
+ source: 'bundled', provider: 'prts-terrarchive', resourceBase: { kind: 'directory', path: fileURLToPath(directory) },
96
+ content: skillBody(await readFile(new URL('SKILL.md', directory), 'utf8')) })
97
+ return () => { retrieval?.(); investigation?.() }
98
+ }
@@ -0,0 +1,348 @@
1
+ /** 云端候选来源 → 本地资料包篇章/官方行号映射。 */
2
+ import { assertCorpusVersion, corpusVersionSnapshot,
3
+ documentGame, documentUid, naturalDocumentTitle } from './store.js'
4
+
5
+ function normalizedStoryIdentifier(value) {
6
+ let identifier = String(value || '').trim().replaceAll('\\', '/')
7
+ if (!identifier) return ''
8
+ identifier = identifier.replace(/^story:\/\//, '').replace(/^story:/, '')
9
+ identifier = identifier.replace(/^.*?gamedata\/story\//, '').replace(/^stories\//, '')
10
+ identifier = identifier.replace(/\.txt(?:(?:#|:|\/)(?:L|line[-_:]?)?\d+)?$/i, '')
11
+ identifier = identifier.replace(/(?:(?:#|:|\/)(?:L|line[-_:]?)\d+)$/i, '')
12
+ return identifier.replace(/^\/+|\/+$/g, '')
13
+ }
14
+
15
+ function sourcePathVariants(value) {
16
+ const variants = new Set()
17
+ for (const part of String(value || '').split(';')) {
18
+ const raw = part.trim().replaceAll('\\', '/').replace(/^\.\//, '')
19
+ if (!raw) continue
20
+ const relative = raw.replace(/^.*?documents\/official_game\//, '')
21
+ .replace(/^.*?official_game\//, '').replace(/^.*?gamedata\/story\//, '')
22
+ variants.add(raw); variants.add(relative)
23
+ if (!relative.startsWith('stories/')) variants.add(`stories/${relative}`)
24
+ if (!/\.txt$/i.test(relative)) {
25
+ variants.add(`${relative}.txt`)
26
+ if (!relative.startsWith('stories/')) variants.add(`stories/${relative}.txt`)
27
+ }
28
+ }
29
+ return [...variants]
30
+ }
31
+
32
+ function canonical(value) {
33
+ if (value === null || typeof value !== 'object') return JSON.stringify(value)
34
+ if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`
35
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`
36
+ }
37
+
38
+ /** 从 cloud_search/cloud_inspect 的任意嵌套响应收集来源提示。 */
39
+ export function collectSourceHints(value, found = new Map()) {
40
+ if (!value || typeof value !== 'object' || found.size >= 200) return [...found.values()]
41
+ if (Array.isArray(value)) {
42
+ for (const item of value) collectSourceHints(item, found)
43
+ return [...found.values()]
44
+ }
45
+ if (value.source_file || value.source_id || value.doc_id || value.document_id
46
+ || value.story_id || value.source_story_id || (value.title && (value.start_line || value.line_range))) {
47
+ const excerpt = [value.content_preview, value.content, value.summary, value.text]
48
+ .find((item) => typeof item === 'string' && item.trim())
49
+ const hint = {
50
+ evidence_id: String(value.evidence_id || ''), candidate_id: String(value.candidate_id || ''),
51
+ game: String(value.game || ''),
52
+ source_file: String(value.source_file || ''), source_id: String(value.source_id || ''),
53
+ doc_id: String(value.doc_id || ''), document_id: String(value.document_id || ''),
54
+ story_id: String(value.story_id || ''), source_story_id: String(value.source_story_id || ''),
55
+ story_code: String(value.story_code || ''), start_line: Number(value.start_line || 0),
56
+ end_line: Number(value.end_line || 0), line_range: String(value.line_range || ''),
57
+ activity_name: String(value.activity_name || ''),
58
+ character_name: String(value.character_name || ''),
59
+ selected_rank: Number(value.selected_rank || 0) || null,
60
+ title: String(value.title || value.story_title || ''), source_type: String(value.source_type || ''),
61
+ score: Number(value.ranking_score || value.score || 0),
62
+ query_variant: String(value.query_variant || ''), excerpt: String(excerpt || '').slice(0, 1600),
63
+ }
64
+ found.set(canonical(hint), hint)
65
+ }
66
+ for (const child of Object.values(value)) collectSourceHints(child, found)
67
+ return [...found.values()]
68
+ }
69
+
70
+ function readableStoryRecord(store, found) {
71
+ const sourceStoryId = String(found?.record?.document?.source_story_id || '')
72
+ if (found?.record?.document?.document_kind !== 'synopsis' || !sourceStoryId.startsWith('[uc]info/')) {
73
+ return Promise.resolve(found)
74
+ }
75
+ return store.getDocumentBySourceStoryId(sourceStoryId.slice('[uc]info/'.length))
76
+ .then((full) => full?.record?.document?.document_kind === 'story' ? full : found)
77
+ }
78
+
79
+ function metadataMatches(store, predicate) {
80
+ const matches = []
81
+ for (const [documentId, location] of store.documents) {
82
+ if (predicate(location.document)) matches.push(documentId)
83
+ }
84
+ return matches
85
+ }
86
+
87
+ function normalizedBasename(value) {
88
+ const path = String(value || '').trim().replaceAll('\\', '/').replace(/[?#].*$/u, '')
89
+ return path.slice(path.lastIndexOf('/') + 1).toLocaleLowerCase()
90
+ }
91
+
92
+ function normalizedSourcePath(value) {
93
+ return String(value || '').trim().replaceAll('\\', '/').replace(/^\.\//u, '')
94
+ .replace(/[?#].*$/u, '').replace(/^\/+|\/+$/gu, '')
95
+ }
96
+
97
+ function documentSourcePaths(document = {}) {
98
+ return [...new Set([document.parent_source_path, document.path]
99
+ .map(normalizedSourcePath).filter(Boolean))]
100
+ }
101
+
102
+ function sourcePathCandidateIds(store, sourceFile, { basenameOnly = false } = {}) {
103
+ const variants = sourcePathVariants(sourceFile).map(normalizedSourcePath).filter(Boolean)
104
+ const basenames = new Set(variants.map(normalizedBasename).filter(Boolean))
105
+ const matches = []
106
+ for (const [documentId, location] of store.documents) {
107
+ const sources = documentSourcePaths(location.document)
108
+ const matched = basenameOnly
109
+ ? sources.some((source) => basenames.has(normalizedBasename(source)))
110
+ : sources.some((source) => variants.includes(source))
111
+ if (matched) matches.push(documentId)
112
+ }
113
+ return matches
114
+ }
115
+
116
+ function metadataHintScore(document, hint) {
117
+ const descriptor = compactText(`${hint.title} ${hint.source_id} ${hint.doc_id}`)
118
+ const activity = compactText(document.activity_name)
119
+ const character = compactText(document.character_name)
120
+ const explicitActivity = compactText(hint.activity_name)
121
+ const explicitCharacter = compactText(hint.character_name)
122
+ let score = 0
123
+ if (explicitActivity) score += explicitActivity === activity ? 400 : -400
124
+ else if (activity && descriptor.includes(activity)) score += 120
125
+ if (explicitCharacter) score += explicitCharacter === character ? 300 : -300
126
+ else if (character && descriptor.includes(character)) score += 80
127
+ const parentStart = Number(document.parent_line_start || 0)
128
+ const parentEnd = Number(document.parent_line_end || 0)
129
+ if (hint.start_line > 0 && parentStart > 0 && parentEnd >= parentStart
130
+ && parentStart <= hint.start_line && hint.start_line <= parentEnd) score += 240
131
+ return score
132
+ }
133
+
134
+ function metadataHintCompatible(document, hint) {
135
+ const activity = compactText(document.activity_name)
136
+ const character = compactText(document.character_name)
137
+ const explicitActivity = compactText(hint.activity_name)
138
+ const explicitCharacter = compactText(hint.character_name)
139
+ if (explicitActivity && activity && explicitActivity !== activity) return false
140
+ if (explicitCharacter && character && explicitCharacter !== character) return false
141
+ return true
142
+ }
143
+
144
+ async function disambiguateSourceCandidates(store, candidateIds, hint) {
145
+ if (!candidateIds.length) return null
146
+ const scored = []
147
+ for (const documentId of candidateIds) {
148
+ const found = await store.getDocument(documentId)
149
+ if (!found || !metadataHintCompatible(found.record.document, hint)) continue
150
+ const excerptMatch = locateExcerpt(found.record, hint.excerpt)
151
+ const excerptScore = excerptMatch.line ? 300 + excerptMatch.score : 0
152
+ scored.push({ found, score: metadataHintScore(found.record.document, hint) + excerptScore })
153
+ }
154
+ if (scored.length === 1) return scored[0].found
155
+ scored.sort((left, right) => right.score - left.score
156
+ || String(left.found.record.document.document_id)
157
+ .localeCompare(String(right.found.record.document.document_id), 'en'))
158
+ if (!scored[0] || scored[0].score <= 0 || scored[0].score === scored[1]?.score) return null
159
+ return scored[0].found
160
+ }
161
+
162
+ /**
163
+ * 同一 story_code 可能同时有行动前、行动后和幕间。优先使用云端文件名中的
164
+ * part_label 消歧;仍有多个候选时,再用云端摘录在各篇本地正文中定位。
165
+ */
166
+ async function disambiguateStoryCode(store, hint) {
167
+ const candidates = metadataMatches(store,
168
+ (document) => String(document.story_code || '') === hint.story_code
169
+ && document.document_type === 'story' && document.document_kind === 'story')
170
+ if (candidates.length === 1) return store.getDocument(candidates[0])
171
+ if (!candidates.length) return null
172
+ const descriptor = `${hint.source_file} ${hint.story_id} ${hint.title}`
173
+ const scored = []
174
+ for (const documentId of candidates) {
175
+ const found = await store.getDocument(documentId)
176
+ if (!found) continue
177
+ const document = found.record.document
178
+ const partMatched = document.part_label && descriptor.includes(document.part_label)
179
+ const titleMatched = document.display_title && descriptor.includes(document.display_title)
180
+ const excerptLine = locateExcerptLine(found.record, hint.excerpt)
181
+ const lineFits = Number(hint.end_line || hint.start_line || 0) <= found.record.lines.length
182
+ scored.push({ found, score: (excerptLine ? 100 : 0) + (partMatched ? 20 : 0)
183
+ + (titleMatched ? 10 : 0) + (lineFits ? 1 : 0) })
184
+ }
185
+ scored.sort((a, b) => b.score - a.score)
186
+ if (!scored[0]?.score || scored[0].score === scored[1]?.score) return null
187
+ return scored[0].found
188
+ }
189
+
190
+ async function locateDocument(store, hint) {
191
+ for (const identifier of [hint.document_id, hint.doc_id, hint.source_id].filter(Boolean)) {
192
+ const direct = await store.getDocument(identifier)
193
+ if (direct) return { found: direct, method: 'document_id' }
194
+ }
195
+ const storyIdentifiers = [hint.source_story_id, hint.story_id, hint.source_file,
196
+ hint.source_id, hint.doc_id].map(normalizedStoryIdentifier).filter(Boolean)
197
+ for (const identifier of storyIdentifiers) {
198
+ const bySourceStory = await store.getDocumentBySourceStoryId(identifier)
199
+ if (bySourceStory) return { found: bySourceStory, method: 'source_story_id' }
200
+ }
201
+ for (const path of sourcePathVariants(hint.source_file)) {
202
+ const byPath = await store.getDocumentByPath(path)
203
+ if (byPath) return { found: byPath, method: 'source_file' }
204
+ }
205
+ const exactSourceIds = sourcePathCandidateIds(store, hint.source_file)
206
+ const exactSource = await disambiguateSourceCandidates(store, exactSourceIds, hint)
207
+ if (exactSource) return { found: exactSource, method: 'parent_source_file' }
208
+ const basenameIds = sourcePathCandidateIds(store, hint.source_file, { basenameOnly: true })
209
+ const basename = await disambiguateSourceCandidates(store, basenameIds, hint)
210
+ if (basename) return { found: basename, method: 'source_file_basename' }
211
+ if (hint.story_code) {
212
+ const found = await disambiguateStoryCode(store, hint)
213
+ if (found) return { found, method: 'story_code' }
214
+ }
215
+ if (hint.title) {
216
+ const ids = store.naturalTitleIndex.get(hint.title) || store.titleIndex.get(hint.title) || []
217
+ if (ids.length === 1) return { found: await store.getDocument(ids[0]), method: 'title' }
218
+ }
219
+ return null
220
+ }
221
+
222
+ const compactText = (value) => String(value || '').normalize('NFKC').replace(/[\s\p{P}\p{S}]+/gu, '')
223
+
224
+ function assertMappingActive(signal) {
225
+ if (signal?.aborted) throw Object.assign(new Error('云端来源映射已取消'), { code: 'CANCELLED' })
226
+ }
227
+
228
+ /** 云端缺可靠行号时,用摘录在已确定篇章内找最可信的官方行。 */
229
+ function locateExcerpt(record, excerpt) {
230
+ const compactExcerpt = compactText(excerpt)
231
+ if (compactExcerpt.length < 4) return { line: 0, score: 0 }
232
+ let best = { line: 0, score: 0 }
233
+ for (const line of record.lines || []) {
234
+ const text = compactText(line.text)
235
+ if (text.length < 4) continue
236
+ if (compactExcerpt.includes(text)) {
237
+ const score = Math.min(text.length, 200)
238
+ if (score > best.score) best = { line: line.line_number, score }
239
+ } else if (text.includes(compactExcerpt)) {
240
+ const score = Math.min(compactExcerpt.length, 200)
241
+ if (score > best.score) best = { line: line.line_number, score }
242
+ }
243
+ }
244
+ return best
245
+ }
246
+
247
+ function locateExcerptLine(record, excerpt) {
248
+ return locateExcerpt(record, excerpt).line
249
+ }
250
+
251
+ function localLineFromSource(document, sourceLine) {
252
+ if (!Number.isInteger(sourceLine) || sourceLine < 1) return 0
253
+ const parentStart = Number(document.parent_line_start || 0)
254
+ const parentEnd = Number(document.parent_line_end || 0)
255
+ if (!parentStart || parentEnd < parentStart) return sourceLine
256
+ if (sourceLine < parentStart || sourceLine > parentEnd) return 0
257
+ const localStart = Number(document.parent_span_local_start || 1)
258
+ return localStart + sourceLine - parentStart
259
+ }
260
+
261
+ /**
262
+ * 将云端来源提示映射为本地稳定文档。模型主要看到 title/document_uid/line,
263
+ * 完整 source_ref 仅留在结构化响应和审计数据中。
264
+ */
265
+ export async function resolveCloudSources(store, hints, { signal } = {}) {
266
+ assertMappingActive(signal)
267
+ await store.ready()
268
+ const snapshot = corpusVersionSnapshot(store)
269
+ assertMappingActive(signal)
270
+ const mappings = []
271
+ const seen = new Set()
272
+ for (const hint of hints.slice(0, 200)) {
273
+ assertMappingActive(signal)
274
+ const located = await locateDocument(store, hint)
275
+ assertMappingActive(signal)
276
+ assertCorpusVersion(store, snapshot)
277
+ if (!located?.found) continue
278
+ const found = await readableStoryRecord(store, located.found)
279
+ assertMappingActive(signal)
280
+ assertCorpusVersion(store, snapshot)
281
+ const record = found.record
282
+ const document = record.document
283
+ const key = `${hint.evidence_id || hint.candidate_id || canonical(hint)}:${document.document_id}`
284
+ if (seen.has(key)) continue
285
+ seen.add(key)
286
+ const requestedStart = Number(hint.start_line || 0)
287
+ let line = localLineFromSource(document, requestedStart)
288
+ let lineMethod = document.parent_line_start ? 'parent_line' : 'cloud_line'
289
+ if (line < 1 || line > record.lines.length) {
290
+ line = locateExcerptLine(record, hint.excerpt)
291
+ lineMethod = line ? 'excerpt_match' : 'document_only'
292
+ }
293
+ const endLine = localLineFromSource(document, Number(hint.end_line || 0))
294
+ const uid = documentUid(document.document_id)
295
+ const title = naturalDocumentTitle(document)
296
+ const titleAmbiguous = (store.naturalTitleIndex.get(title) || []).length > 1
297
+ const recommendedLocator = titleAmbiguous ? { document_uid: uid } : { title }
298
+ mappings.push({
299
+ game: documentGame(document),
300
+ document_id: String(document.document_id || ''),
301
+ source_ref_prefix: String(document.source_ref_prefix || ''),
302
+ document_uid: uid, title,
303
+ display_title: String(document.display_title || ''),
304
+ title_ambiguous: titleAmbiguous,
305
+ document_type: String(document.document_type || ''), document_kind: String(document.document_kind || ''),
306
+ activity_name: String(document.activity_name || ''), story_name: String(document.story_name || ''),
307
+ character_name: String(document.character_name || ''), line_count: Number(document.line_count || record.lines.length),
308
+ parent_source_path: String(document.parent_source_path || ''),
309
+ line: line || null, line_end: endLine > 0 && endLine <= record.lines.length ? endLine : null,
310
+ source_ref: line ? `${document.source_ref_prefix}:L${line}` : null,
311
+ suggested_source_ref: line ? `${document.source_ref_prefix}:L${line}` : '',
312
+ start_line: line || 0,
313
+ end_line: endLine > 0 && endLine <= record.lines.length ? endLine : line || 0,
314
+ line_range: line ? (endLine > line && endLine <= record.lines.length ? `${line}-${endLine}` : `${line}`) : '',
315
+ mapping_method: located.method, line_method: lineMethod,
316
+ evidence_id: hint.evidence_id, candidate_id: hint.candidate_id,
317
+ selected_rank: hint.selected_rank, source_type: hint.source_type,
318
+ excerpt: hint.excerpt,
319
+ recommended_read: line
320
+ ? { ...recommendedLocator, line, before: 4, after: 8 }
321
+ : { ...recommendedLocator, mode: 'document' },
322
+ })
323
+ }
324
+ assertCorpusVersion(store, snapshot)
325
+ return mappings
326
+ }
327
+
328
+ /** 本地映射不可用时保留云端答案并注明核验缺口;取消仍终止整次调用。 */
329
+ export async function attachLocalSourceMappings(store, response, { signal } = {}) {
330
+ assertMappingActive(signal)
331
+ if (!response || response.status === 'error') return response
332
+ const hints = collectSourceHints(response.data ?? response)
333
+ if (!hints.length) return response
334
+ let mappings
335
+ try {
336
+ mappings = await resolveCloudSources(store, hints, { signal })
337
+ } catch (error) {
338
+ assertMappingActive(signal)
339
+ if (error?.code === 'CANCELLED' || error?.name === 'AbortError') throw error
340
+ response.local_source_mapping_warning = {
341
+ code: 'LOCAL_SOURCE_MAPPING_UNAVAILABLE',
342
+ message: '云端检索已完成,但本地原文映射暂不可用;请在设置中安装或检查资料包后再核验原文。',
343
+ }
344
+ return response
345
+ }
346
+ if (mappings.length) response.local_source_mappings = mappings
347
+ return response
348
+ }