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,332 @@
1
+ /** DSH 会话内的轻量证据状态:只驻留内存,按 exec.agent 隔离。 */
2
+ import { wikiSectionRanges } from './wiki.js'
3
+ import { readableRenderedLine } from './read.js'
4
+ import { randomBytes } from 'node:crypto'
5
+
6
+ const MAX_CANDIDATES = 240
7
+ const MAX_MAPPINGS = 240
8
+ const MAX_DOCUMENTS = 96
9
+ const MAX_LINES = 4000
10
+
11
+ function newState() {
12
+ const suffix = randomBytes(8).toString('hex')
13
+ return {
14
+ intentId: `dsh-${suffix}`,
15
+ cloudIntentId: `prts-${suffix}`,
16
+ lastCloudRequestId: '',
17
+ searchCandidates: [],
18
+ cloudSourceMappings: [],
19
+ readCoverage: [],
20
+ documents: new Map(),
21
+ completedSearchCalls: new Map(),
22
+ dataVersion: null,
23
+ }
24
+ }
25
+
26
+ function collectVisibleText(value, output = []) {
27
+ if (!value || typeof value !== 'object') return output
28
+ if (value.type === 'text' && typeof value.text === 'string') output.push(value.text)
29
+ if (Array.isArray(value)) for (const item of value) collectVisibleText(item, output)
30
+ else for (const child of Object.values(value)) collectVisibleText(child, output)
31
+ return output
32
+ }
33
+
34
+ /** 当前模型可见 surface 中的工具结果正文;压缩或 spill 后不再含原文的节点不能复用。 */
35
+ export function visibleToolResults(agent) {
36
+ const session = agent?.session
37
+ const nodes = session?.surface?.nodes
38
+ if (typeof session?.eventAt !== 'function' || !nodes
39
+ || typeof nodes[Symbol.iterator] !== 'function') return new Map()
40
+ const visible = new Map()
41
+ for (const seq of nodes) {
42
+ const event = session.eventAt(seq)
43
+ if (event?.type !== 'tool/result') continue
44
+ const callId = event.data?.message?.source?.callId
45
+ if (typeof callId === 'string' && callId) {
46
+ visible.set(callId, collectVisibleText(event.data.message?.content).join('\n'))
47
+ }
48
+ }
49
+ return visible
50
+ }
51
+
52
+ export function createEvidenceStateRegistry() {
53
+ const contexts = new WeakMap()
54
+ return {
55
+ forExecution(exec, dataVersion = null) {
56
+ const agent = exec?.agent
57
+ const owner = agent && (typeof agent === 'object' || typeof agent === 'function')
58
+ ? agent
59
+ : exec && (typeof exec === 'object' || typeof exec === 'function') ? exec : null
60
+ // 没有可识别的宿主会话对象时宁可放弃跨调用复用,也不能让不同请求共享证据。
61
+ if (!owner) {
62
+ const state = newState()
63
+ state.dataVersion = dataVersion
64
+ return state
65
+ }
66
+ let state = contexts.get(owner)
67
+ if (!state) {
68
+ state = newState()
69
+ contexts.set(owner, state)
70
+ }
71
+ if (dataVersion && state.dataVersion && state.dataVersion !== dataVersion) {
72
+ state = newState()
73
+ contexts.set(owner, state)
74
+ }
75
+ if (dataVersion) state.dataVersion = dataVersion
76
+ return state
77
+ },
78
+ }
79
+ }
80
+
81
+ export function rememberSearchCandidates(state, response) {
82
+ if (!Array.isArray(response?.documents)) return
83
+ for (const document of response.documents) for (const match of document.matches || []) {
84
+ const candidate = {
85
+ title: String(document.title || ''), line: Number(match.line_start || 0),
86
+ }
87
+ const key = `${candidate.title}:${candidate.line}`
88
+ const index = state.searchCandidates.findIndex((item) => item._key === key)
89
+ if (index >= 0) state.searchCandidates[index] = { ...candidate, _key: key }
90
+ else state.searchCandidates.push({ ...candidate, _key: key })
91
+ }
92
+ if (state.searchCandidates.length > MAX_CANDIDATES) {
93
+ state.searchCandidates.splice(0, state.searchCandidates.length - MAX_CANDIDATES)
94
+ }
95
+ }
96
+
97
+ export function rememberCloudMappings(state, response) {
98
+ const requestId = String(response?.data?.request?.request_id || response?.data?.request_id || '')
99
+ if (requestId) state.lastCloudRequestId = requestId
100
+ for (const mapping of response?.local_source_mappings || []) {
101
+ const key = `${mapping.evidence_id || mapping.candidate_id || ''}:${mapping.document_id}`
102
+ const compact = { ...mapping, _key: key }
103
+ const index = state.cloudSourceMappings.findIndex((item) => item._key === key)
104
+ if (index >= 0) state.cloudSourceMappings[index] = compact
105
+ else state.cloudSourceMappings.push(compact)
106
+ }
107
+ if (state.cloudSourceMappings.length > MAX_MAPPINGS) {
108
+ state.cloudSourceMappings.splice(0, state.cloudSourceMappings.length - MAX_MAPPINGS)
109
+ }
110
+ }
111
+
112
+ function sourceRefParts(sourceRef) {
113
+ const match = String(sourceRef || '').match(/^(.*):L([1-9][0-9]*)$/u)
114
+ return match ? { prefix: match[1], line: Number(match[2]) } : null
115
+ }
116
+
117
+ /** 将已转换的 corpus_read contract 解析为稳定篇章与请求行区间。 */
118
+ export async function resolveReadWindow(store, contract) {
119
+ await store.ready()
120
+ const locator = contract?.locator || {}
121
+ const parsed = sourceRefParts(locator.source_ref)
122
+ let documentId = String(locator.document_id || '')
123
+ if (!documentId && locator.document_uid) {
124
+ documentId = String(store.getDocumentIdByUid(locator.document_uid) || '')
125
+ }
126
+ if (!documentId && parsed) documentId = String(store.getDocumentIdByPrefix(parsed.prefix) || '')
127
+ if (!documentId && locator.display_title) {
128
+ // 工具面(corpus_search / corpus_read)的 title 是自然语言完整标题
129
+ // (naturalTitleIndex);原始 display_title 仅作回退。此前只查 titleIndex,
130
+ // 自然标题一律解析失败,读取覆盖去重对最常见的标题路径完全失效。
131
+ // 同名歧义时不跟踪(读取本身会给出消歧错误或稳定选择)。
132
+ const title = String(locator.display_title)
133
+ const ids = store.naturalTitleIndex.get(title) || store.titleIndex.get(title) || []
134
+ if (ids.length === 1) documentId = ids[0]
135
+ }
136
+ if (!documentId) return null
137
+ const lineCount = Number(store.documents.get(documentId)?.document?.line_count || Infinity)
138
+ const boundedEnd = (line) => Math.min(Number(line), lineCount)
139
+ const selection = contract.selection || {}
140
+ if (selection.mode === 'around') {
141
+ const center = parsed?.line || Number(selection.center_line || 0)
142
+ if (!center || center > lineCount) return null
143
+ // 未显式给 before/after 时按契约默认 ±3 计窗口(normalizeReadRequest 的
144
+ // 默认值);此前按 0 计,重复请求的回放只剩中心 1 行,丢失上下文。
145
+ const before = Number(selection.before_lines ?? 3)
146
+ const after = Number(selection.after_lines ?? 3)
147
+ return { documentId, lineStart: Math.max(1, center - before),
148
+ lineEnd: boundedEnd(center + after) }
149
+ }
150
+ if (selection.mode === 'range') return { documentId,
151
+ lineStart: Number(selection.start_line), lineEnd: boundedEnd(selection.end_line) }
152
+ if (selection.mode === 'section') {
153
+ const found = await store.getDocument(documentId)
154
+ if (found?.record.document.document_type !== 'knowledge'
155
+ || found.record.document.document_kind !== 'wiki') return null
156
+ const ranges = found ? wikiSectionRanges(found.record, [selection.section]) : []
157
+ if (ranges.length !== 1) return null
158
+ return { documentId, lineStart: ranges[0].start_line,
159
+ lineEnd: boundedEnd(ranges[0].end_line) }
160
+ }
161
+ if (selection.mode === 'document' && !selection.cursor) {
162
+ const start = Number(selection.start_line || 1)
163
+ return { documentId, lineStart: start,
164
+ lineEnd: boundedEnd(start + Number(contract.limits?.max_lines || 100) - 1) }
165
+ }
166
+ return null
167
+ }
168
+
169
+ /** 覆盖判定用的行标记必须与 renderRead 的模型可见格式逐字一致(含剥离说话人前缀与行尾空白)。 */
170
+ function renderedLineMarker(line) {
171
+ return readableRenderedLine(line)
172
+ }
173
+
174
+ function lineVisible(cached, lineNumber, visibleToolResults) {
175
+ if (!cached?.lines.has(lineNumber) || !visibleToolResults?.size) return false
176
+ const sources = cached.lineSources?.get(lineNumber)
177
+ const marker = renderedLineMarker(cached.lines.get(lineNumber))
178
+ return Boolean(sources && [...sources].some((callId) =>
179
+ String(visibleToolResults.get(callId) || '').includes(marker)))
180
+ }
181
+
182
+ export function coveredRead(state, requested, visibleToolResults) {
183
+ if (!requested) return null
184
+ const cached = state.documents.get(requested.documentId)
185
+ for (let line = requested.lineStart; line <= requested.lineEnd; line += 1) {
186
+ if (!lineVisible(cached, line, visibleToolResults)) return null
187
+ }
188
+ return requested
189
+ }
190
+
191
+ /** 计算请求区间中已经核验与仍需从资料包读取的连续片段。 */
192
+ export function planReadCoverage(state, requested, visibleToolResults) {
193
+ if (!requested || requested.lineEnd < requested.lineStart) return null
194
+ const reusedRanges = []
195
+ const cached = state.documents.get(requested.documentId)
196
+ for (let line = requested.lineStart; line <= requested.lineEnd; line += 1) {
197
+ if (!lineVisible(cached, line, visibleToolResults)) continue
198
+ const previous = reusedRanges.at(-1)
199
+ if (previous?.lineEnd === line - 1) previous.lineEnd = line
200
+ else reusedRanges.push({ lineStart: line, lineEnd: line })
201
+ }
202
+ const unreadRanges = []
203
+ let cursor = requested.lineStart
204
+ for (const range of reusedRanges) {
205
+ if (cursor < range.lineStart) unreadRanges.push({ lineStart: cursor, lineEnd: range.lineStart - 1 })
206
+ cursor = Math.max(cursor, range.lineEnd + 1)
207
+ }
208
+ if (cursor <= requested.lineEnd) unreadRanges.push({ lineStart: cursor, lineEnd: requested.lineEnd })
209
+ return { reusedRanges, unreadRanges }
210
+ }
211
+
212
+ function mergeCoverage(state, entry) {
213
+ const overlaps = state.readCoverage.filter((item) => item.documentId === entry.documentId
214
+ && item.lineEnd + 1 >= entry.lineStart && entry.lineEnd + 1 >= item.lineStart)
215
+ if (overlaps.length) {
216
+ entry.lineStart = Math.min(entry.lineStart, ...overlaps.map((item) => item.lineStart))
217
+ entry.lineEnd = Math.max(entry.lineEnd, ...overlaps.map((item) => item.lineEnd))
218
+ state.readCoverage = state.readCoverage.filter((item) => !overlaps.includes(item))
219
+ }
220
+ state.readCoverage.push(entry)
221
+ }
222
+
223
+ export function rememberRead(state, response, { callId = '', store = null } = {}) {
224
+ if (response?.status !== 'ok' || response.duplicate_read || response.content?.format !== 'lines') return
225
+ const groups = new Map()
226
+ for (const line of response.content.lines || []) {
227
+ const documentId = String(line.document_id || response.document?.document_id || '')
228
+ const lineNumber = Number(line.line_number)
229
+ if (!documentId || !Number.isInteger(lineNumber) || lineNumber < 1) continue
230
+ const group = groups.get(documentId) || { lines: [], lineStart: lineNumber, lineEnd: lineNumber }
231
+ group.lines.push(line)
232
+ group.lineStart = Math.min(group.lineStart, lineNumber)
233
+ group.lineEnd = Math.max(group.lineEnd, lineNumber)
234
+ groups.set(documentId, group)
235
+ }
236
+ for (const [documentId, group] of groups) {
237
+ let cached = state.documents.get(documentId)
238
+ if (!cached) {
239
+ const document = documentId === response.document?.document_id
240
+ ? response.document : store?.documents?.get(documentId)?.document || { document_id: documentId }
241
+ cached = { document: structuredClone(document), lines: new Map(), lineSources: new Map(),
242
+ dataVersion: response.data_version || null, integrity: structuredClone(response.integrity || {}) }
243
+ state.documents.set(documentId, cached)
244
+ }
245
+ for (const line of group.lines) {
246
+ const lineNumber = Number(line.line_number)
247
+ cached.lines.set(lineNumber, structuredClone(line))
248
+ if (callId) {
249
+ let sources = cached.lineSources.get(lineNumber)
250
+ if (!sources) {
251
+ sources = new Set()
252
+ cached.lineSources.set(lineNumber, sources)
253
+ }
254
+ sources.add(callId)
255
+ }
256
+ }
257
+ mergeCoverage(state, { documentId, lineStart: group.lineStart, lineEnd: group.lineEnd })
258
+ }
259
+ while (state.documents.size > MAX_DOCUMENTS
260
+ || [...state.documents.values()].reduce((sum, item) => sum + item.lines.size, 0) > MAX_LINES) {
261
+ const oldest = state.documents.keys().next().value
262
+ state.documents.delete(oldest)
263
+ state.readCoverage = state.readCoverage.filter((item) => item.documentId !== oldest)
264
+ }
265
+ }
266
+
267
+ export function replayCoveredRead(state, requested, contract) {
268
+ const cached = state.documents.get(requested.documentId)
269
+ if (!cached) return null
270
+ const lines = [...cached.lines.values()].filter((line) => line.line_number >= requested.lineStart
271
+ && line.line_number <= requested.lineEnd).sort((a, b) => a.line_number - b.line_number)
272
+ if (!lines.length || lines[0].line_number > requested.lineStart
273
+ || lines.at(-1).line_number < requested.lineEnd) return null
274
+ const characterCount = lines.reduce((sum, line) => sum + String(line.text || '').length, 0)
275
+ // 缓存命中仍服从本次预算;更小的预算交回标准读取器计算实际页边界。
276
+ if (lines.length > (contract.limits?.max_lines ?? 100)
277
+ || characterCount > (contract.limits?.max_chars ?? 12000)) return null
278
+ const documentMode = contract.selection.mode === 'document'
279
+ const total = documentMode ? cached.document.line_count : requested.lineEnd - requested.lineStart + 1
280
+ const hasMore = documentMode && requested.lineEnd < total
281
+ return {
282
+ contract_version: 'prts-corpus-tools-v1', status: 'ok', duplicate_read: true,
283
+ data_version: cached.dataVersion, normalized_request: contract,
284
+ document: structuredClone(cached.document),
285
+ selection: { mode: contract.selection.mode, line_start: requested.lineStart,
286
+ line_end: requested.lineEnd, line_count: lines.length, character_count: characterCount,
287
+ ...(contract.selection.mode === 'section' ? { wiki_section: contract.selection.section } : {}),
288
+ truncated: hasMore },
289
+ content: { format: 'lines', lines },
290
+ page: { limit: contract.limits?.max_lines ?? 100, returned: lines.length,
291
+ has_more: hasMore, next_cursor: null, total, total_relation: 'eq' },
292
+ integrity: structuredClone(cached.integrity),
293
+ stats: { elapsed_ms: 0, scanned_documents: 0, scanned_lines: 0, returned_chars: 0,
294
+ estimated_input_tokens: 0, truncated: hasMore, reused_local_evidence: true },
295
+ warnings: [],
296
+ guidance: '该范围已在当前模型可见的上文工具结果中完整出现;这里复用已核验的本地原文,不要再次读取相同范围。',
297
+ }
298
+ }
299
+
300
+ /**
301
+ * 标准读取器先计算本次预算和续页,再从该页移除模型上文中已经可见的行。
302
+ * 去重只改变交付正文,不改变已确定的页边界;coverage 明示旧行的来源。
303
+ */
304
+ export function reuseReadCoverage(response, plan) {
305
+ if (!plan?.reusedRanges.length || !plan.unreadRanges.length
306
+ || response?.status !== 'ok' || response.content?.format !== 'lines') return response
307
+ const lines = response.content.lines.filter((line) => plan.unreadRanges.some((range) =>
308
+ line.line_number >= range.lineStart && line.line_number <= range.lineEnd))
309
+ const characterCount = lines.reduce((sum, line) => sum + String(line.text || '').length, 0)
310
+ const publicRanges = (ranges) => ranges.map((range) => ({
311
+ line_start: range.lineStart, line_end: range.lineEnd,
312
+ }))
313
+ return {
314
+ ...response, partial_read_reused: true,
315
+ selection: { ...response.selection, line_count: lines.length, character_count: characterCount },
316
+ content: { format: 'lines', lines },
317
+ coverage: {
318
+ requested_range: { line_start: response.selection.line_start, line_end: response.selection.line_end },
319
+ reused_ranges: publicRanges(plan.reusedRanges),
320
+ fetched_ranges: publicRanges(plan.unreadRanges),
321
+ complete: true,
322
+ },
323
+ page: { ...response.page, returned: lines.length },
324
+ stats: { ...response.stats, returned_chars: characterCount,
325
+ estimated_input_tokens: Math.ceil(characterCount / 2.5),
326
+ reused_local_evidence: true,
327
+ },
328
+ warnings: [...(response.warnings || []), { code: 'PARTIAL_READ_REUSED',
329
+ message: `本页已有 ${plan.reusedRanges.length} 段经过核验,本次只返回新读取行。` }],
330
+ guidance: '本页与当前模型可见的上文原文部分重叠;这里只返回新读取行,coverage.reused_ranges 对应旧行就在上方可见工具结果中。',
331
+ }
332
+ }